Hacking
home

C언어 초보에서 중수까지1 - 매크로

Introduction

평소에도 low-level언어를 좋아하는 편이라(RUST도 관심이 있긴 하지만 용기가 없다..) C를 자주 사용했지만 스레드 아키텍처 설계나 빌드 시스템 이용이 서툴러서 아쉬움이 있었는데 읽고 싶었던 Extreme C 책이 번역서(아직 초판이라 책 이름은 쓰지 않겠다)로 나와서 정리하려고 한다.
C언어 기본 문법에 대한 내용(포인터, 함수 등등)보다는 트릭이나 설계와 관련된 내용 위주(내가 헷갈리거나 몰랐던 내용 ㅎㅎ..)로 소개하겠다.
선수지식 ! ● 컴퓨터 아키텍처 지식: 메모리, CPU, 주변 장치 ● 프로그래밍 기초 지식: 알고리즘, 산술연산 작동방식 ● 터미널과 셸 명령어 사용법: 유닉스 계열 ● 프로그래밍 중급 지식: 구조체, 클래스, C or C++의 포인터 ● 객체지향 프로그래밍 기초 지식

전처리기 지시자

전처리는 컴파일러로 보내기 전 소스 코드를 만들고 수정할 수 있도록 하는 과정이다. C 전처리기는 지시자를 사용해 통제하는데, # 문자로 시작하는 코드이다.

매크로

매크로는 다음과 같은 상황에 활용할 수 있다.
상수 정의하기
C 함수를 작성하지 않고 함수로 사용하기
루프 풀기loop unrolling
헤더 가드header guard
코드 생성
조건부 컴파일

매크로 정의하기

매크로는 #define 지시자를 이용해 정의한다. 각 매크로는 이름과 사용 가능한 매개변수 리스트를 가지는데, 이 값은 매크로 확장이라는 단계를 통해 대체된다.
다음 예제를 보자.
#define ABC 5 int main(int argc, char** argv) { int x = 2; int y = ABC; int z = x + y; return 0; }
C
복사
이 코드가 매크로 확장 단계 이후에 다음과 같이 바뀐다.
int main(int argc, char** argv) { int x = 2; int y = 5; int z = x + y; return 0; }
C
복사
이제 다른 예제를 소개하겠다.
#define ADD(a, b) a + b int main(int argc, char** argv) { int x = 2; int y = 3; int z = ADD(x, y); return 0; }
C
복사
이 코드에서 ADD는 유사 함수 매크로라고 부른다. 전처리 이후의 코드 결과는 다음과 같다.
int main(int argc, char** argv) { int x = 2; int y = 3; int z = x + y; return 0; }
C
복사
이제 조금 더 복잡한 예제를 확인해보자.
#include <stdio.h> #define PRINT(a) printf("%d\n", a); #define LOOP(v, s, e) for (int v = s; v <= e; v++) { #define ENDLOOP } int main(int argc, char** argv) { LOOP(counter, 1, 10) PRINT(counter) ENDLOOP return 0; }
C
복사
해당 코드의 최종 전처리 이후 소스 코드는 다음과 같다.
... ... content of stdio.h ... ... int main(int argc, char** argv) { for (int counter = 1; counter <= 10; counter++) { printf("%d\n", counter); } return 0; }
C
복사
다음과 같은 방식으로 코딩하는것은 도메인 특화 언어(DSL)를 정의하고 DSL을 이용해 코드를 작성하는 것 인데, 이는 매크로의 중요한 활용법이다.(구글 테스트 프레임 워크와 같은 곳에서 활용)
또 한가지 눈여겨 봐야 할 점은 #include 역시 전처리기 이므로 해당 헤더파일의 내용으로 대체되었다는 점 이다.
다음 예제에서는 매크로 매개변수의 두 가지 연산자를 소개한다.( #, ##)
#include <stdio.h> #include <string.h> #define CMD(NAME) \ char NAME ## _cmd[256] = ""; \ strcpy(NAME ## _cmd, #NAME); int main(int argc, char** argv) { CMD(copy) CMD(paste) CMD(cut) return 0; }
C
복사
매크로를 확장할 때 # 연산자는 매개변수를 한 쌍의 따옴표로 둘러싼 문자 형태로 변환하고, ## 연산자는 매개변수와 다른 요소를 문자열로 결합해 변수 이름을 만든다.
... ... content of stdio.h ... ... ... content of string.h ... ... int main(int argc, char** argv) { char copy_cmd[256] = ""; strcpy(copy_cmd, "copy"); char paste_cmd[256] = ""; strcpy(paste_cmd, "paste"); char cut_cmd[256] = ""; strcpy(cut_cmd, "cut"); return 0; }
C
복사
매크로를 작성할때 \(역슬래시)를 사용하면 다음 행에서도 동일한 매크로를 작성할 수 있다.

가변 인자 매크로

가변 인자 매크로는 바로 예제로 설명하겠다.
#include <stdio.h> #include <stdlib.h> #include <string.h> #define VERSION "2.3.4" #define LOG_ERROR(format, ...) \ fprintf(stderr, format, __VA_ARGS__) int main(int argc, char** argv) { if (argc < 3) { LOG_ERROR("Invalid number of arguments for version %s\n.", VERSION); exit(1); } if (strcmp(argv[1], "-n") != 0) { LOG_ERROR("%s is a wrong param at index %d for version %s.", argv[1], 1, VERSION); exit(1); } // ... return 0; }
C
복사
__VA_ARGS__식별자는 매개변수에 할당되지 않은 나머지 입력 인수로 모두 교체한다.
... ... content of stdio.h ... ... ... content of stdlib.h ... ... ... content of string.h ... ... int main(int argc, char** argv) { if (argc < 3) { fprintf(stderr, "Invalid number of arguments for version %s\n.", "2.3.4"); exit(1); } if (strcmp(argv[1], "-n") != 0) { fprintf(stderr, "%s is a wrong param at index %d for version %s.", argv[1],1, "2.3.4"); exit(1); } // ... return 0; }
C
복사