Menu

C의 명령줄 인자: argc, argv, 그리고 안전하게 파싱하기

C 프로그램이 명령줄을 읽는 방법을 살펴봅니다. argc와 argv의 구조, 인자 순회, argv[0]에 담긴 값, atoi 대신 strtol로 숫자 변환하기, 그리고 이 모두를 모아 만든 작은 계산기까지.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

터미널에서 실행하는 모든 프로그램은 인자를 받을 수 있습니다. gcc -Wall hello.c -o hello는 네 개를 넘기죠. 여러분이 만든 C 프로그램은 main의 두 매개변수를 통해 자기 인자를 읽으며, 그것이 메커니즘의 전부입니다. 라이브러리 호출도, 준비 작업도 없습니다.

시그니처

지금까지 이 문서의 모든 예제는 int main(void)를 썼습니다. 또 다른 표준 형태는 명령줄을 받습니다.

int main(int argc, char *argv[]) {
    /* ... */
}
  • argc("argument count")는 인자의 개수이며, 프로그램 이름을 포함합니다.
  • argv("argument vector")는 문자열 배열입니다. char *argv[]는 "char를 가리키는 포인터의 배열", 곧 C 문자열의 배열을 뜻합니다.

char **argv라는 표기도 그만큼 자주 보게 되는 동등한 형태입니다. 배열 매개변수는 포인터로 변환되므로 둘은 같은 것을 선언합니다.

편집기의 Args 패널로 인자를 넣고(필드 하나가 인자 하나입니다) Run을 누르세요. hello, world, 42 세 개의 필드로 넣어 보세요.

구조 뜯어보기

./greet Ada Lovelace를 실행하면 이렇게 됩니다.

인덱스
argv[0]"./greet"호출될 때 쓰인 프로그램 이름
argv[1]"Ada"첫 번째 실제 인자
argv[2]"Lovelace"두 번째 실제 인자
argv[3]NULL종료 표시
argc3argv[0]을 포함한 개수

기억해 둘 만한 사실이 넷 있습니다.

  • argc는 실무상 최소 1입니다. argv[0]이 프로그램 이름이기 때문이죠. 실제 인자는 인덱스 1부터 시작하며, 그래서 인자를 도는 반복문은 i = 1에서 시작합니다.
  • 모든 것이 문자열입니다. ./prog 42는 숫자 42가 아니라 문자 두 개로 이루어진 "42"를 줍니다. 변환은 여러분 몫입니다.
  • argv[argc]NULL입니다. 표준이 보장하므로, 원한다면 argc 없이도 배열을 훑을 수 있습니다. for (char **p = argv + 1; *p != NULL; p++)처럼요.
  • 단어를 쪼개는 것은 셸이지 여러분의 프로그램이 아닙니다. ./prog "Ada Lovelace"는 인자 하나입니다. 따옴표는 셸이 소비하며 argv까지 도달하지 않습니다.

읽기 전에 확인하기

전달되지 않은 인자는 빈 문자열이 아닙니다. 건드릴 권한이 없는 메모리입니다. 언제나 argc부터 검증하세요.

이 짧은 프로그램에 유닉스 도구 전반의 표준 관례 두 가지가 들어 있습니다.

  • 사용법 메시지는 stdout이 아니라 **stderr**로 보냅니다. 어딘가로 파이프되는 출력을 오염시키지 않기 위해서입니다.
  • main0이 아닌 반환값은 프로그램을 실행한 쪽에 실패를 알립니다. 0은 성공이고 그 외의 값은 뭔가 잘못됐다는 뜻입니다. 셸 스크립트가 이 값을 확인합니다.

이름을 하드코딩하지 않고 argv[0]을 출력하면, 프로그램 이름이 바뀌었거나 다른 경로로 실행됐더라도 메시지가 사용자가 실제로 호출한 방식과 일치합니다.

숫자 인자 변환하기

argv[1]은 텍스트입니다. 산술을 하려면 변환해야 하고, 쓸 함수는 <stdlib.h>의 **strtol**입니다.

Args 필드 세 개로 10, 20, 30을 추가해 보고, 그다음 그중 하나를 banana로 바꿔 오류 경로를 확인해 보세요.

strtol의 세 번째 매개변수는 진법입니다. 10진수면 10, 16진수면 16, 0x 접두사나 앞의 0을 보고 자동으로 판단하게 하려면 0입니다. 두 번째는 함수가 소비하지 않은 첫 문자를 가리키는 포인터를 저장할 곳이며, 바로 이것이 오류 검사를 가능하게 해 줍니다.

  • end == text는 아무것도 파싱하지 못했다는 뜻입니다. 인자가 숫자로 시작하지 않은 것이죠.
  • *end != '\0'은 숫자 뒤에 남은 텍스트가 있다는 뜻이라, "12abc"가 조용히 12가 되는 대신 거부됩니다.
  • errno == ERANGE는 값이 long을 넘쳤다는 뜻입니다.

이와 비교되는 atoi는 더 짧지만 위의 어느 것도 알려 주지 못합니다.

int n = atoi(argv[1]);   /* "banana" -> 0, "0" -> 0, 오버플로 -> 미정의 */

atoi는 잘못된 입력에 0을 반환하는데, 이는 진짜 0과 구별할 수 없습니다. 입력이 프로그램 바깥에서 올 때는 언제나 strtol을(부동소수점이라면 strtod를) 쓰세요. 자세한 내용은 문자열 변환에 있습니다.

작은 계산기

지금까지의 내용을 합쳐, 숫자와 연산자와 숫자를 받는 프로그램을 만들어 봅니다.

12 + 30을 넣어 보고, 그다음 7 x 6, 그다음 5 / 0을 시도해 보세요.

op == "+"가 아니라 strcmp(op, "+") == 0이라는 점에 주목하세요. char * 값을 ==로 비교하면 내용이 아니라 주소를 비교하므로, 텍스트가 똑같아도 거짓이 될 수 있습니다. 문자열 함수를 참고하세요.

* 대신 x를 쓴 것은 현실적인 디테일입니다. 대부분의 셸은 프로그램이 실행되기도 전에 홀로 놓인 *를 현재 디렉터리의 파일 목록으로 확장합니다. 따옴표로 감싸도('*') 되지만, 셸이 무시하는 문자를 고르는 편이 더 친절합니다.

플래그 파싱하기

옵션은 관례적으로 -로 시작합니다. 직접 작성한 간단한 반복문으로 흔한 경우들을 처리할 수 있습니다.

Args 필드 네 개로 -v, -n, 3, hello를 넣어 보세요.

argv[++i] 앞의 i + 1 >= argc 검사가 중요한 줄입니다. 값을 기대하는 옵션은 그 값이 실제로 있는지 확인해야 하고, 그렇지 않으면 -n이 마지막 인자일 때 배열의 끝을 넘어 읽게 됩니다. 실제 유닉스 시스템에서는 <unistd.h>getopt-vn3 같은 묶음 단축 옵션까지 포함해 이 모두를 대신 해 줍니다. 플래그가 두세 개를 넘어가면 꺼내 쓸 만합니다.

브라우저 편집기에서의 인자

이 페이지의 편집기 블록은 알아서 컴파일되고 실행되며, Args 패널이 터미널에서 프로그램 이름 뒤에 올 내용을 입력하는 곳입니다. Add arg로 추가한 필드 하나하나가 정확히 argv의 한 항목이 됩니다. 중간에 셸이 없으므로 공백으로 나뉘지 않고 따옴표도 필요 없습니다. hello world가 든 필드는 hello world라는 하나의 인자로 도착합니다. -n 3 hello를 넘기려면 필드 세 개를 추가하세요. 패널을 비워 두면 argc == 1이 되는데, 그래서 처음 실행할 때 argc < 2 가드가 걸리고, 또 그래서 그 가드를 써 둘 가치가 있는 것입니다.

흔한 실수

  • argc를 확인하지 않고 argv[1] 읽기. 인자가 전달되지 않았을 때 미정의 동작이며, 이 영역에서 가장 흔한 크래시입니다.
  • 반복문을 i = 0에서 시작하기. 프로그램 이름을 데이터인 양 처리하게 됩니다.
  • argv[1]을 숫자로 취급하기. 변환하기 전까지는 텍스트입니다.
  • 사용자 입력에 atoi 쓰기. 잘못된 인자와 0을 구별할 방법이 없습니다.
  • 문자열을 ==로 비교하기. strcmp를 쓰세요.
  • argv 문자열에 쓰기. 표준이 수정을 허용하긴 하지만 공간이 제한적이고 플랫폼마다 다릅니다. 무언가를 바꿔야 한다면 자기 버퍼로 복사하세요.

자주 묻는 질문

C에서 argc와 argv는 무엇인가요?

int main(int argc, char *argv[])의 두 매개변수입니다. argc는 프로그램 이름을 포함한 명령줄 인자의 개수이고, argv는 그 인자들을 문자열로 담은 배열입니다. argv[0]은 프로그램 이름, argv[1]은 첫 번째 실제 인자이며, argv[argc]는 항상 NULL입니다.

C 프로그램에 인자는 어떻게 전달하나요?

터미널에서 실행 파일 이름 뒤에 적으면 됩니다. ./program hello 42처럼요. 그러면 argc == 3이 되고 argv[1]은 문자열 "hello", argv[2]는 문자열 "42"가 됩니다. 이 페이지의 편집기에서는 터미널 대신 Args 패널에 인자를 입력합니다.

C에서 명령줄 인자를 int로 변환하려면 어떻게 하나요?

strtol을 쓰세요. char *end; long n = strtol(argv[1], &end, 10);이라고 쓴 뒤, end가 여전히 시작 위치를 가리키고 있지는 않은지(아무것도 파싱되지 않음), 그리고 *end'\0'인지(뒤에 찌꺼기가 없음) 확인합니다. atoi는 더 짧지만 "0""banana" 모두에 0을 반환하므로 오류를 알려 줄 수 없습니다.

C에서 argv[0]은 무엇인가요?

프로그램이 호출될 때 쓰인 이름으로, 보통 ./program이나 전체 경로입니다. 사용법 메시지(fprintf(stderr, "usage: %s FILE\n", argv[0]))에 쓰면 사용자가 실제로 호출한 방식과 메시지가 일치해서 유용합니다. 드물게 빈 문자열일 수도 있으니 항상 존재한다고 가정하지는 마세요.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기