좋은 C 메인 함수를 작성하는 방법

2025. 1. 11. 09:02프로그래밍

728x90

C 파일을 구조화하고 챔피언처럼 명령줄 인수를 처리하는 C 기본 함수를 작성하는 방법을 배워 봅시다.

 

요즘에는 젊은 애덜이 Python과 JavaScript로 정말 멋진 자신 만의 "앱"을 작성하고 있다는 것을 인정해야 겠군요.

하지만 너무 성급하게 C를 내팽개치진 마세요. C는 줄게 엄청 많은 유능하고 간결한 언어니까요.

속도가 필요한 경우 C로 작성하는 것이 답이 될 수도 있을 것입니다.

안정된 직업이나 혹은 널 포인터 역참조 사냥 법을 배울 기회를 찾고 있다면 C가 당신의 답이 될 수도 있습니다!

 

이 기사에서는 저는 C 파일을 구성하는 방법과 챔피언처럼 명령줄 인수를 처리하는 C 기본 함수를 작성하는 방법을 설명 할 것입니다.

 

저요?: 딱딱한 유닉스 시스템 프로그래머.

당신?: 편집기, C 컴파일러 그리고 시간 죽일 일을 찾는 사람.

 

Let's do this.

해보자구요.

 

지루하지만 정확한 C 프로그램

C 프로그램은 main.c라는 파일에 저장되어 main() 함수로 시작 하는 것이 일반적입니다.

/* main.c */
int main(int argc, char *argv[]) {

}
 

이 프로그램은 컴파일되지만 아무 작업도 수행하지 않죠.

$ gcc main.c
$ ./a.out -o foo -vv
$
 

정확하고 지루하네요.

 

주요 함수들은 유일하다.

main() 함수는 당신의 프로그램 구동 될 때 실행되는 프로그램의 첫 번째 함수이지만, 제일 첫 번째로실행되는 함수는 아닙니다. 그 첫 번째 함수라는 것은 당신의 프로그램이 컴파일될 때 자동으로 연결되는 C 런타임 라이브러리가 일반적으로 제공하는 _start()입니다.

세세한 내용은 운영 체제 및 컴파일러 툴 체인에 크게 의존하고 있기에 위 내용은 말하지 않았다고 치죠.

 

main() 함수에는 전통적으로 #argc 및 #argv 라고 하는 두 개의 인수가 있으며 부호 있는 정수를 반환합니다. 대부분의 Unix 환경에서는 프로그램이 성공 시 0(영)을 반환하고 실패 시 -1(음수 1)을 반환할 것으로 예상합니다.

 
인수
이름
설명
argc
Argument count
인수 벡터의 개수
argv
Argument vector
문자 포인터들의 배열

 

인수 벡터 argv는 프로그램을 호출한 명령줄의 토큰화된 표현입니다.

위의 예에서 argv는 다음 문자열의 목록처럼 될 수도 있을 것입니다:

argv = [ "/path/to/a.out", "-o", "foo", "-vv" ];
 

인수 벡터는 항상 첫 번째 인덱스인 argv[0]에 실행된 프로그램의 전체 경로를 가지고 있어, 최소한 하나의 문자열을 포함하도록 보장됩니다.

 

main.c 파일 해부

처음부터 main.c를 작성하면 일반적으로 다음과 같이 구성됩니다:

/* main.c */
/* 0 copyright/licensing */
/* 1 includes */
/* 2 defines */
/* 3 external declarations */
/* 4 typedefs */
/* 5 global variable declarations */
/* 6 function prototypes */

int main(int argc, char *argv[]) {
/* 7 command-line parsing */
}

/* 8 function declarations */
 

0을 제외하고 그 아래 각 섹션 번호를 매겨 이에 대해 이야기 할 것입니다.

소스에 저작권이나 라이센스 텍스트를 넣어야 한다면 거기에 넣으세요.

 

또 하나, 당신의 프로그램에 추가 되는 것 중에 주석은 언급하지 않을 것입니다.

"Comments lie."
- A cynical but smart and good looking programmer.
 

주석 대신 의미 있는 함수 및 변수 이름을 사용하십시오.

프로그래머의 태생적인 게으름에 기대어서 일단 주석을 추가하기 시작하면, 유지 관리 부하가 두 배가 됩니다.

만약 해당 코드를 변경하거나 리팩터링해야 하는 경우 주석을 업데이트하거나 확장해야 합니다.

시간이 흐르면서, 그 코드는 주석이 설명하는 것과 비슷한 그 어떤과는 전혀 다른 것으로 커버리겠죠.

 

만약 주석을 작성해야 하는 경우도 코드가 수행하는 작업에 대해 작성하지 마십시오. 대신 코드가 수행하는 작업을 수행하는 이유에 대해 작성하십시오.

이 코드에 대한 모든 것을 잊어버려서 지금으로부터 5년 후에 읽고 싶은 댓글을 작성하십시오.

그리고 세상의 운명은 당신에게 달려 있겠지요. 부담 가지지 마세요.

 

1. Includes

내가 main.c 파일에 추가하는 첫 번째 항목은 내 프로그램에서 사용할 수 있는 다양한 표준 C 라이브러리 함수 및 변수를 만드는 것입니다.

표준 C 라이브러리는 많은 일을 합니다. /usr/include의 헤더 파일을 찾아서 이 파일로 무엇을 할 수 있는지 알아보십시오.

#include 문자열은 참조된 파일 전체를 현재 파일에 포함시키는 C 전처리기(cpp) 지시문입니다.

C의 헤더 파일은 일반적으로 .h 확장자로 이름이 지정되며 실행 가능한 코드를 포함해서는 안 됩니다.

오직 매크로, 정의, typedef, 외부 변수 및 함수 프로토타입만 해당됩니다.

 

문자열 <header.h>는 cpp에게 시스템 정의 헤더 경로(일반적으로 /usr/include)에서 header.h라는 파일을 찾도록 지시합니다.

/* main.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <libgen.h>
#include <errno.h>
#include <string.h>
#include <getopt.h>
#include <sys/types.h>
 

아래 목록은 다음 항목에 대해 기본적으로 포함할 최소 전역 include 세트입니다.

 
#include 파일
제공하는 함수들
stdio
FILE, stdin, stdout, stderr, 그리고 fprint() 의 패밀리 함수 제공
stdlib
malloc(), calloc(), 그리고 realloc() 함수 제공
unistd
EXIT_FAILURE, EXIT_SUCCESS 제공
libgen
basename() 함수 제공
errno
external errno 변수 및 이에 포함 된 모든 값들의 정의
string
memcpy(), memset(), 그리고 strlen() 패밀리 함수들 제공
getopt
getopt() 함수와 external optarg, opterr, optind 제공
sys/types
uint32_t and uint64_t 같은 Typedef 단축형 제공

 

2. Defines

/* main.c */
<...>

#define OPTSTR "vi:o:f:h"
#define USAGE_FMT  "%s [-v] [-f hexflag] [-i inputfile] [-o outputfile] [-h]"
#define ERR_FOPEN_INPUT  "fopen(input, r)"
#define ERR_FOPEN_OUTPUT "fopen(output, w)"
#define ERR_DO_THE_NEEDFUL "do_the_needful blew up"
#define DEFAULT_PROGNAME "george"
 

지금 당장은 이치에 맞지 않아보이지만 OPTSTR define 은 프로그램이 권장할 명령줄 스위치를 명시할 것입니다. OPTSTR이 getopt()의 동작에 어떤 영향을 미칠 것인지 알아보려면 getopt(3) 매뉴얼 페이지를 참조하십시오.

USAGE_FMT define 은 usage() 함수에서 참조되는 printf() 스타일 형식 문자열입니다.

또한 저는 파일의 이 부분에서 문자열 상수를 #define으로 모아 두는 걸 좋아합니다.

이를 모아두면 맞춤법 수정, 메시지 재사용, 필요 시 메시지 국제화를 더 쉽게 수행할 수 있습니다.

마지막으로 변수 및 함수 이름과 구별하기 위해 #define의 이름을 지정할 때 모두 대문자를 사용하십시오.

원하는 경우 단어를 한데 붙이거나 밑줄로 단어를 구분할 수 있습니다; 모두 대문자인지 확인해 보십시오.

 

3. External 선언

/* main.c */
<...>

extern int errno;
extern char *optarg;
extern int opterr, optind;
 

extern 선언은 해당 이름을 현재 컴파일 단위(일명 "파일")의 네임스페이스로 가져오고 프로그램이 해당 변수에 액세스할 수 있도록 해 줍니다.

여기서 우리는 세 개의 정수 변수와 하나의 문자 포인터에 대한 정의를 가져왔습니다.

opt 접두사 변수는 getopt() 함수에서 사용되며 errno는 표준 C 라이브러리에서 out-of-band 소통 채널로 사용되며 함수가 실패한 이유를 전달합니다.

 

4. Typedefs

/* main.c */
<...>

typedef struct {
  int           verbose;
  uint32_t      flags;
  FILE         *input;
  FILE         *output;
} options_t;
 

external 선언 후에는 구조체, 공용체 및 열거형에 대해 typedef를 선언하는 것을 좋아합니다.

typedef로 이름을 지정하는 것은 그 자체로 종교입니다; 나는 이름으로 type임을 나타내기 위해 _t 접미사를 사용하는 것을 강력히 선호합니다.

이 예에서는 options_t를 멤버가 4개인 구조체로 선언했습니다.

C는 공백 중립 프로그래밍 언어이므로 동일한 열에 필드 이름을 정렬하기 위해 공백을 사용합니다.

나는 이런 표현 방식을 좋아합니다. 포인터 선언의 경우 이름 앞에 별표를 추가하여 포인터임을 분명히 합니다.

 

5. 전역 변수 선언

/* main.c */
<...>

int dumb_global_variable = -11;
 

전역 변수는 나쁜 생각이므로 절대 사용해서는 안 됩니다.

하지만 전역 변수를 사용해야 하는 경우 여기에서 선언하고 기본값을 지정해야 합니다. 진심으로 전역 변수를 사용하지 마십시오.

 

6. 함수 프로토타입

/* main.c */
<...>

void usage(char *progname, int opt);
int  do_the_needful(options_t *options);
 

함수를 작성할 때 main() 함수 이전이 아니라 이후에 함수를 추가하고 여기에 함수 프로토타입을 포함하십시오.

초기 C 컴파일러는 단일 패스 전략(single-pass strategy)을 사용했습니다. 즉, 프로그램에서 사용한 모든 기호(변수 또는 함수 이름)를 사용하기 전에 선언해야 했습니다.

최신 컴파일러는 코드를 생성하기 전에 완전한 심볼 테이블을 작성하는 거의 모든 다중 패스 컴파일러이므로 함수 프로토타입을 사용할 필요가 없습니다.

하지만 때때로 코드에 사용 할 컴파일러를 선택할 수 없을 수 있으므로 함수 프로토타입을 작성하고 계속 진행합니다.

 

물론 명령줄로 전달한 내용을 이해하지 못할 때 main()이 호출하는 usage() 함수를 항상 포함합니다.

 

7. 명령줄 구문 분석

/* main.c */
<...>

int main(int argc, char *argv[]) {
    int opt;
    options_t options = { 0, 0x0, stdin, stdout };

    opterr = 0;

    while ((opt = getopt(argc, argv, OPTSTR)) != EOF)
       switch(opt) {
           case 'i':
              if (!(options.input = fopen(optarg, "r")) ){
                 perror(ERR_FOPEN_INPUT);
                 exit(EXIT_FAILURE);
                 /* NOTREACHED */
              }
              break;

           case 'o':
              if (!(options.output = fopen(optarg, "w")) ){
                 perror(ERR_FOPEN_OUTPUT);
                 exit(EXIT_FAILURE);
                 /* NOTREACHED */
              }    
              break;
             
           case 'f':
              options.flags = (uint32_t )strtoul(optarg, NULL, 16);
              break;

           case 'v':
              options.verbose += 1;
              break;

           case 'h':
           default:
              usage(basename(argv[0]), opt);
              /* NOTREACHED */
              break;
       }

    if (do_the_needful(&options) != EXIT_SUCCESS) {
       perror(ERR_DO_THE_NEEDFUL);
       exit(EXIT_FAILURE);
       /* NOTREACHED */
    }

    return EXIT_SUCCESS;
}
 

좋아요, 많네요.

main() 함수의 목적은 사용자가 제공하는 인수를 수집하고 최소한의 입력 유효성 검사를 수행한 다음 수집된 인수를 사용할 함수에 전달하는 것입니다.

이 예제는 기본값으로 초기화된 변수 options를 선언하고 명령줄을 구문 분석하여 필요에 따라 options를 업데이트하는 것입니다.

 

이 main() 함수의 핵심은 getopt()를 사용하여 argv를 통해 명령줄 옵션과 해당 인수(있는 경우)를 찾는 while 루프입니다.

파일 앞부분의 OPTSTR #define은 getopt()의 동작을 유도하는 템플릿입니다.

opt 변수는 getopt()에서 찾은 모든 명령줄 옵션의 문자 값을 취하고 명령줄 옵션 감지에 대한 프로그램의 응답은 switch 문에서 발생합니다.

 

주의를 기울이는 사람들은 왜 opt가 32비트 int로 선언되었지만 8비트 문자가 사용 되었는 지 의문을 가질 것입니다.

내가 EOF(파일 끝 마커)에 대해 확인하도록 argv의 끝에 도달하면 음수 값을 갖는 int를 반환하도록 getopt()는 되어 있는 것으로 확인 됩니다.

문자는 부호 있는 수이지만 변수를 함수 반환 값에 일치시키는 것을 좋아 합니다.

 

정의 된 명령줄 옵션이 감지되면 옵션별 동작이 발생합니다.

OPTSTR 뒤 콜론이 있는 일부 options은 인수가 지정되어 있습니다.

옵션에 인수가 있는 경우 argv의 다음 문자열은 외부에서 정의된 변수 optarg를 통해 가져와서 프로그램에 사용할 수 있습니다.

저는 optarg를 사용하여 읽기 및 쓰기를 위해 파일을 열거나 명령줄 인수를 문자열에서 정수 값으로 변환합니다.

 

다음은 스타일에 대한 몇 가지 사항이 있습니다:

 

  • opterr을 0으로 초기화하여 getopt가 ?를 뱉지 않도록 합니다.
  • main() 중간에 exit(EXIT_FAILURE) ; 또는 종료(EXIT_SUCCESS); 사용 합니다.
  • /* NOTREACHED */는 내가 좋아하는 lint 지시문입니다.
  • int를 반환하는 함수의 끝에서 return EXIT_SUCCESS를 사용 합니다.
  • 암시적 type 변환을 명시적으로 캐스팅합니다.

이 프로그램의 명령줄 예제는 컴파일된 경우 다음과 같이 사용 할 수 있을 것입니다:

$ ./a.out -h
a.out [-v] [-f hexflag] [-i inputfile] [-o outputfile] [-h]
 

사실 이것이 바로 usage()가 일단 컴파일되면 stderr로 내보낼 것입니다.

 

8. 함수 선언

/* main.c */
<...>

void usage(char *progname, int opt) {
   fprintf(stderr, USAGE_FMT, progname?progname:DEFAULT_PROGNAME);
   exit(EXIT_FAILURE);
   /* NOTREACHED */
}

int do_the_needful(options_t *options) {

   if (!options) {
     errno = EINVAL;
     return EXIT_FAILURE;
   }

   if (!options->input || !options->output) {
     errno = ENOENT;
     return EXIT_FAILURE;
   }

   /* XXX do needful stuff */

   return EXIT_SUCCESS;
}
 

마지막으로 저는 boilerplate 코드가 아닌 함수를 작성합니다.

(BoilerPlate 코드란 모든 코드를 작성하기 위해 항상 필요한 부분을 의미한다)

이 예제에서 함수 do_the_needful()은 options_t 구조체에 대한 포인터를 받아들입니다.

옵션 포인터가 NULL이 아닌지 확인한 다음 계속해서 input 및 output 구조체 멤버의 유효성을 검사합니다.

테스트가 실패하면 EXIT_FAILURE가 반환되고 외부 전역 변수 errno를 일반적인 오류 코드로 설정하여 호출자에게 일반적인 이유를 알립니다.

perror()는 호출자가 errno의 값을 기반으로 사람이 읽을 수 있는 오류 메시지를 내보내는 데 사용할 수 있는 편리한 함수 입니다.

 

함수는 거의 항상 어떤 식으로든 입력의 유효성을 검사해야 합니다. 전체 검증이 비용이 많이 드는 경우 한 번 검증하고 검증된 데이터는 변경 될 수 없도록 만들어 둡니다.

usage() 함수는 fprintf() 호출에서 조건부 할당을 사용하여 progname 인수의 유효성을 검사합니다.

usage() 함수는 어쨋거나 종료될 것이므로 errno를 설정하거나 올바른 프로그램 이름을 사용하는 것은 그닥 큰 내용은 아닙니다.

 

여기서 내가 피하고자 하는 큰 오류는 NULL 포인터를 역참조하는 것입니다. 이로 인해 운영 체제가 SYSSEGV라는 내 프로세스에 특수 신호를 보내 피할 수 없는 비정상 죽음(종료)를 초래합니다.

사용자가 마지막으로 보고 싶어하는 것은 SYSSEGV로 인한 충돌이 되겠죠.

더 나은 오류 메시지를 내보내고 프로그램을 정상적으로 종료하려한다면 이 NULL 포인터를 잡는 것이 훨씬 낫겠죠?

 

어떤 사람들은 함수 본문에 여러 개의 return 문이 있는 것에 대해 불평합니다. 그들은 "제어 흐름의 연속성" 및 기타 항목에 대한 부분에도 의문을 제기 하더군요.

솔직히 만약 함수 중간 부에서 문제가 발생하면 오류 조건을 반환하는 바로 좋은 시점이 됩니다.

한 번만 반환되도록 수많은 중첩 if 문을 작성하는 것은 결코 "좋은 생각"™이 아닙니다.

 

마지막으로 4개 이상의 인수를 사용하는 함수를 작성하는 경우 인수를 구조체로 묶고 해당 구조체에 대한 포인터를 전달하는 것이 더 낫습니다.이렇게 하면 함수 시그너처(서명)이 더 간단해지며 기억하기 쉬워지고 나중에 호출될 때 망가지지 않습니다.

 

또한 함수의 스택 프레임에 복사해야 하는 항목이 적기 때문에 함수 호출이 약간 더 빨라집니다.

실제로 이것은 함수가 수백만 또는 수십억 번 호출되는 경우에만 고려 사항이 될 것이지만요.

말이 안 되더라도 걱정하지 마세요.

 

잠깐, 주석이 없다구요!?!!

/* XXX do needful stuff */
 

do_the_needful() 함수에서 코드를 문서화하는 대신 placeholder로 설계된 특정 유형의 주석을 작성했습니다. 그 영역에 있을 때, 때때로 어떤 특정 형편없는 코드를 멈추고 작성하는 것을 원치 않습니다. 지금은 아니고, 당신은 나중에 여기로 돌아와서 작성하게 되겠죠.

거기에 약간의 빵 부스러기를 남길 것입니다.

 

XXX 접두사가 있는 주석과 수행해야 할 작업을 설명하는 짧은 설명을 삽입합니다.

나중에 시간이 더 있으면 소스 파일 상에서 이 XXX를 찾아보게 될 것입니다.

어떻게 사용 하던지 간에, 예를 들어 함수 이름이나 변수로서 다른 컨텍스트의 코드베이스에 표시되어 있지 않는지 확인 하십시오.

 

함께 모아서

/* main.c - the complete listing */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <libgen.h>
#include <errno.h>
#include <string.h>
#include <getopt.h>

#define OPTSTR "vi:o:f:h"
#define USAGE_FMT  "%s [-v] [-f hexflag] [-i inputfile] [-o outputfile] [-h]"
#define ERR_FOPEN_INPUT  "fopen(input, r)"
#define ERR_FOPEN_OUTPUT "fopen(output, w)"
#define ERR_DO_THE_NEEDFUL "do_the_needful blew up"
#define DEFAULT_PROGNAME "george"

extern int errno;
extern char *optarg;
extern int opterr, optind;

typedef struct {
  int           verbose;
  uint32_t      flags;
  FILE         *input;
  FILE         *output;
} options_t;

int dumb_global_variable = -11;

void usage(char *progname, int opt);
int  do_the_needful(options_t *options);

int main(int argc, char *argv[]) {
    int opt;
    options_t options = { 0, 0x0, stdin, stdout };

    opterr = 0;

    while ((opt = getopt(argc, argv, OPTSTR)) != EOF)
       switch(opt) {
           case 'i':
              if (!(options.input = fopen(optarg, "r")) ){
                 perror(ERR_FOPEN_INPUT);
                 exit(EXIT_FAILURE);
                 /* NOTREACHED */
              }
              break;

           case 'o':
              if (!(options.output = fopen(optarg, "w")) ){
                 perror(ERR_FOPEN_OUTPUT);
                 exit(EXIT_FAILURE);
                 /* NOTREACHED */
              }    
              break;
             
           case 'f':
              options.flags = (uint32_t )strtoul(optarg, NULL, 16);
              break;

           case 'v':
              options.verbose += 1;
              break;

           case 'h':
           default:
              usage(basename(argv[0]), opt);
              /* NOTREACHED */
              break;
       }

    if (do_the_needful(&options) != EXIT_SUCCESS) {
       perror(ERR_DO_THE_NEEDFUL);
       exit(EXIT_FAILURE);
       /* NOTREACHED */
    }

    return EXIT_SUCCESS;
}

void usage(char *progname, int opt) {
   fprintf(stderr, USAGE_FMT, progname?progname:DEFAULT_PROGNAME);
   exit(EXIT_FAILURE);
   /* NOTREACHED */
}

int do_the_needful(options_t *options) {

   if (!options) {
     errno = EINVAL;
     return EXIT_FAILURE;
   }

   if (!options->input || !options->output) {
     errno = ENOENT;
     return EXIT_FAILURE;
   }

   /* XXX do needful stuff */

   return EXIT_SUCCESS;
}
 

좋습니다. 이 프로그램은 컴파일하고 실행한다면 여전히 거의 아무것도 하지 않습니다.

하지만 당신은 이제 당신만의 명령 줄 분석 C 프로그램을 빌드 할 수 있는 견고한 코드 뼈대를 가지게 된 것입니다.

 

그럼 윈도우에서는?

윈도우에서는 getopt 같은 것이 없기 때문에 구현 해야 될 수도 있습니다만, 많이 돌아 다니기 때문에 걱정 할 필요가 없습니다.

 

윈도우에서는 대략 아래처럼 구성 하면 될 것 같습니다.

테스트는 해보지 않았습니다.

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <cstdint>
#include "getopt.h"

#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)

#define OPTSTR "vi:o:f:h"
#define USAGE_FMT  "%s [-v] [-f hexflag] [-i inputfile] [-o outputfile] [-h]"
#define ERR_FOPEN_INPUT  "fopen(input, r)"
#define ERR_FOPEN_OUTPUT "fopen(output, w)"
#define ERR_DO_THE_NEEDFUL "do_the_needful blew up"
#define DEFAULT_PROGNAME "george"

extern int errno;
extern char* optarg;
extern int opterr, optind;

typedef struct {
    int           verbose;
    uint32_t      flags;
    FILE* input;
    FILE* output;
} options_t;

int dumb_global_variable = -11;

void usage(char* progname, int opt);
int  do_the_needful(options_t* options);

int main(int argc, char* argv[]) {
    int opt;
    options_t options = { 0, 0x0, stdin, stdout };

    opterr = 0;

    while ((opt = getopt(argc, argv, OPTSTR)) != EOF)
        switch (opt) {
        case 'i':
            /*if (!(options.input = fopen(optarg, "r"))) {*/
            if (!(fopen_s(&options.input, optarg, "r"))) {
                perror(ERR_FOPEN_INPUT);
                exit(EXIT_FAILURE);
                /* NOTREACHED */
            }
            break;

        case 'o':
            /*if (!(options.output = fopen(optarg, "w"))) {*/
            if (!(fopen_s(&options.output, optarg, "w"))) {
                perror(ERR_FOPEN_OUTPUT);
                exit(EXIT_FAILURE);
                /* NOTREACHED */
            }
            break;

        case 'f':
            options.flags = (uint32_t)strtoul(optarg, NULL, 16);
            break;

        case 'v':
            options.verbose += 1;
            break;

        case 'h':
        default:
            {
                char buf[_MAX_FNAME];
                char fname[_MAX_FNAME];
                char extn[_MAX_FNAME];
                /*usage(basename(argv[0]), opt);*/
                _splitpath_s(argv[0], NULL, 0, NULL, 0, fname, sizeof(fname), extn, sizeof(extn));
                usage(fname, opt);
                /* NOTREACHED */
            }
            
            break;
        }

    if (do_the_needful(&options) != EXIT_SUCCESS) {
        perror(ERR_DO_THE_NEEDFUL);
        exit(EXIT_FAILURE);
        /* NOTREACHED */
    }

    return EXIT_SUCCESS;
}

void usage(char* progname, int opt) {
    fprintf(stderr, USAGE_FMT, progname ? progname : DEFAULT_PROGNAME);
    exit(EXIT_FAILURE);
    /* NOTREACHED */
}

int do_the_needful(options_t* options) {

    if (!options) {
        errno = EINVAL;
        return EXIT_FAILURE;
    }

    if (!options->input || !options->output) {
        errno = ENOENT;
        return EXIT_FAILURE;
    }

    /* XXX do needful stuff */

    return EXIT_SUCCESS;
}
 

그리고 getopt.h 파일은 아래와 같습니다.

#ifndef GETOPT_H
#define GETOPT_H

#ifdef __cplusplus
extern "C"
{
#endif


extern int opterr, /* if error message should be printed */
optind, /* index into parent argv vector */
optopt, /* character checked for validity */
optreset; /* reset getopt */
extern char *optarg; /* argument associated with option */

int getopt(int nargc, char* const nargv[], const char* ostr);

#ifdef __cplusplus
}
#endif


#endif
 
 

음... 일반적으로 MSVC 같은 경우에는 메인 함수를 만들면 cpp 확장자로 만들어지기 때문에, getopt 가 C 소스인 경우를 고려해서 extern "C" 가 필요 합니다.

MS2019 용으로 한 번 만들어 보았습니다.

cmdlineparser.zip
0.01MB

 

이상.

 

 

728x90