수색…


소개

모든 선행 처리기 명령은 해시 (파운드) 기호 # 합니다. AC 매크로는 #define 전 처리기 지시문을 사용하여 정의 된 전처리 기 명령입니다. 전처리 단계에서 C 전처리 기 (C 컴파일러의 일부)는 이름이 나타나는 모든 곳에서 매크로 본문을 단순히 대체합니다.

비고

컴파일러가 코드에서 매크로를 발견하면 간단한 문자열 교체를 수행하므로 추가 작업이 수행되지 않습니다. 이 때문에 전 처리기에 의한 변경은 C 프로그램의 범위를 존중하지 않습니다. 예를 들어, 매크로 정의는 블록 내에있는 것으로 제한되지 않으므로 블록 문을 종료하는 '}' 영향을받지 않습니다.

전처리 기는 설계 상 완벽하지 않지만 전처리기만으로는 수행 할 수없는 몇 가지 계산 유형이 있습니다.

보통 컴파일러는 전처리 단계 후에 컴파일을 중지하고 결과를 검사 할 수있는 명령 행 플래그 (또는 구성 설정)를 가지고 있습니다. POSIX 플랫폼에서이 플래그는 -E 입니다. 그래서이 플래그로 gcc를 실행하면 확장 된 소스가 stdout에 출력됩니다 :

$ gcc -E cprog.c

종종 전처리 기는 컴파일러에 의해 호출되는 별도의 프로그램으로 구현되며 해당 프로그램의 일반 이름은 cpp 입니다. 많은 수의 전처리 기는 줄 번호에 대한 정보와 같은 지원 정보를 방출합니다.이 정보는 디버깅 정보를 생성하기 위해 컴파일의 후속 단계에서 사용됩니다. 프리 프로세서가 gcc를 기반으로하는 경우, -P 옵션은 이러한 정보를 표시하지 않습니다.

$ cpp -P cprog.c

조건부 포함 및 조건부 함수 서명 수정

조건부로 코드 블록을 포함하려면 전 처리기에 여러 가지 지시문 (예 : #if , #ifdef , #else , #endif 등)이 있어야합니다.

/* Defines a conditional `printf` macro, which only prints if `DEBUG`
 * has been defined
 */
#ifdef DEBUG
#define DLOG(x) (printf(x))
#else
#define DLOG(x)
#endif

#if 조건에 대해 일반 C 관계 연산자를 사용할 수 있습니다.

#if __STDC_VERSION__ >= 201112L
/* Do stuff for C11 or higher */
#elif __STDC_VERSION__ >= 199901L
/* Do stuff for C99 */
#else
/* Do stuff for pre C99 */
#endif

#if 지시문은 C if 문과 유사하게 동작하며 정수 상수 식만 포함하고 캐스트는 포함하지 않습니다. 하나의 추가적인 단항 연산자 인 defined( identifier ) 지원하며 식별자가 정의되어 있으면 1 을 반환하고 그렇지 않으면 0 반환합니다.

#if defined(DEBUG) && !defined(QUIET)
#define DLOG(x) (printf(x))
#else
#define DLOG(x)
#endif

조건부 서명 서명 수정

대부분의 경우 응용 프로그램의 릴리스 빌드에는 가능한 적은 오버 헤드가있을 것으로 예상됩니다. 그러나 임시 빌드를 테스트하는 동안 발견 된 문제에 대한 추가 로그와 정보가 도움이 될 수 있습니다.

예를 들어 테스트 빌드를 수행 할 때 사용법에 대한 로그를 생성하는 SHORT SerOpPluAllRead(PLUIF *pPif, USHORT usLockHnd) 함수가 있다고 가정합니다. 그러나이 함수는 여러 위치에서 사용되며 로그를 생성 할 때 정보의 일부는 호출되는 함수의 위치를 ​​알아야합니다.

조건부 컴파일을 사용하면 함수를 선언하는 포함 파일에서 다음과 같은 것을 사용할 수 있습니다. 이렇게하면 함수의 표준 버전이 함수의 디버그 버전으로 바뀝니다. 전 처리기는 함수 SerOpPluAllRead() 에 대한 호출을 함수 SerOpPluAllRead_Debug() 에 대한 두 개의 추가 인수, 파일 이름 및 함수가 사용되는 행 번호로 대체하는 데 사용됩니다.

조건부 컴파일은 표준 함수를 디버그 버전으로 대체할지 여부를 선택하는 데 사용됩니다.

#if 0
// function declaration and prototype for our debug version of the function.
SHORT   SerOpPluAllRead_Debug(PLUIF *pPif, USHORT usLockHnd, char *aszFilePath, int nLineNo);

// macro definition to replace function call using old name with debug function with additional arguments.
#define SerOpPluAllRead(pPif,usLock) SerOpPluAllRead_Debug(pPif,usLock,__FILE__,__LINE__)
#else
// standard function declaration that is normally used with builds.
SHORT   SerOpPluAllRead(PLUIF *pPif, USHORT usLockHnd);
#endif

이를 통해 함수의 표준 버전 인 SerOpPluAllRead() 를 파일의 이름과 함수가 호출 된 파일의 줄 번호를 제공하는 버전으로 대체 할 수 있습니다.

여기에는 중요한 고려 사항이 있습니다. 이 함수를 사용하는 모든 파일에는 전처리 기가 함수를 수정하기 위해이 접근법이 사용되는 헤더 파일이 포함되어야합니다. 그렇지 않으면 링커 오류가 표시됩니다.

함수의 정의는 다음과 같습니다. 이 소스가하는 일은 전 처리기가 SerOpPluAllRead() 함수의 이름을 SerOpPluAllRead()SerOpPluAllRead_Debug() 두 개의 추가 인수, 함수가 호출 된 파일의 이름에 대한 포인터 및 행 번호를 포함하도록 인수 목록을 수정하도록 요청하는 것입니다 함수가 사용되는 파일에서.

#if defined(SerOpPluAllRead)
// forward declare the replacement function which we will call once we create our log.
SHORT    SerOpPluAllRead_Special(PLUIF *pPif, USHORT usLockHnd);

SHORT    SerOpPluAllRead_Debug(PLUIF *pPif, USHORT usLockHnd, char *aszFilePath, int nLineNo)
{
    int iLen = 0;
    char  xBuffer[256];

    // only print the last 30 characters of the file name to shorten the logs.
    iLen = strlen (aszFilePath);
    if (iLen > 30) {
        iLen = iLen - 30;
    }
    else {
        iLen = 0;
    }

    sprintf (xBuffer, "SerOpPluAllRead_Debug(): husHandle = %d, File %s, lineno = %d", pPif->husHandle, aszFilePath + iLen, nLineNo);
    IssueDebugLog(xBuffer);

    // now that we have issued the log, continue with standard processing.
    return SerOpPluAllRead_Special(pPif, usLockHnd);
}

// our special replacement function name for when we are generating logs.
SHORT    SerOpPluAllRead_Special(PLUIF *pPif, USHORT usLockHnd)
#else
// standard, normal function name (signature) that is replaced with our debug version.
SHORT   SerOpPluAllRead(PLUIF *pPif, USHORT usLockHnd)
#endif
{
    if (STUB_SELF == SstReadAsMaster()) {
        return OpPluAllRead(pPif, usLockHnd);
    } 
    return OP_NOT_MASTER;
}

소스 파일 포함

#include 전처리 지시문의 가장 일반적인 용도는 다음과 같습니다.

#include <stdio.h>
#include "myheader.h"

#include 는 참조 된 파일의 내용으로 명령문을 대체합니다. 꺽쇠 괄호 (<>)는 시스템에 설치된 헤더 파일을 가리키는 반면 따옴표 ( "")는 사용자가 제공하는 파일을 의미합니다.

다음 예제와 같이 매크로 자체는 다른 매크로를 한 번 확장 할 수 있습니다.

#if VERSION == 1
    #define INCFILE  "vers1.h"
#elif VERSION == 2
    #define INCFILE  "vers2.h"
    /*  and so on */
#else
    #define INCFILE  "versN.h"
#endif
/* ... */
#include INCFILE

매크로 대체

가장 간단한 매크로 대체 형식은 다음과 같이 manifest constant 를 정의하는 manifest constant 입니다.

#define ARRSIZE 100
int array[ARRSIZE];

변수를 10 곱하고 새 값을 저장하는 함수와 같은 매크로를 정의합니다.

#define TIMES10(A) ((A) *= 10)

double b = 34;
int c = 23;

TIMES10(b);   // good: ((b) *= 10);
TIMES10(c);   // good: ((c) *= 10);
TIMES10(5);   // bad:  ((5) *= 10);

대체는 프로그램 텍스트의 다른 해석보다 먼저 수행됩니다. TIMES10 대한 첫 번째 호출에서 정의에서 이름 Ab 로 대체되고 확장 된 텍스트가 호출 위치에 놓입니다. 이 TIMES10 정의는 TIMES10 과 같습니다.

#define TIMES10(A) ((A) = (A) * 10)

이것은 A 의 교체를 두 번 평가할 수 있기 때문에 원하지 않는 부작용을 일으킬 수 있습니다.

다음은 값이 인수의 최대 값 인 함수와 유사한 매크로를 정의합니다. 함수 호출의 오버 헤드없이 인라인 코드를 생성하고 인수의 호환 가능한 유형을 위해 작업하는 장점이 있습니다. 두 번째 인수 (부작용 포함)를 여러 번 평가하고 여러 번 호출 할 경우 함수보다 많은 코드를 생성하는 단점이 있습니다.

#define max(a, b) ((a) > (b) ? (a) : (b))

int maxVal = max(11, 43);              /* 43 */
int maxValExpr = max(11 + 36, 51 - 7); /* 47 */

/* Should not be done, due to expression being evaluated twice */
int j = 0, i = 0;
int sideEffect = max(++i, ++j);       /* i == 4 */

이 때문에 매크로의 인수를 여러 번 평가하는 매크로는 일반적으로 프로덕션 코드에서 피할 수 있습니다. C11 이후에는 그러한 다중 호출을 피할 수있는 _Generic 기능이 있습니다.

매크로 확장의 괄호 (정의의 오른쪽)는 인수와 결과식이 올바르게 바인딩되고 매크로가 호출되는 컨텍스트에 잘 맞는지 확인합니다.

오류 지시문

선행 처리기가 #error 지시문을 발견하면 컴파일이 중지되고 포함 된 진단 메시지가 인쇄됩니다.

#define DEBUG

#ifdef DEBUG
#error "Debug Builds Not Supported"
#endif

int main(void) {
    return 0;
}

가능한 출력 :

$ gcc error.c
error.c: error: #error "Debug Builds Not Supported"

코드 섹션을 차단하려면 #if 0

삭제하려는 코드 섹션이 있거나 일시적으로 사용 중지하려는 코드 섹션이있는 경우 블록 주석으로 주석 처리 할 수 ​​있습니다.

/* Block comment around whole function to keep it from getting used.
 * What's even the purpose of this function?
int myUnusedFunction(void)
{
    int i = 5;
    return i;
}
*/

그러나 블록 주석으로 둘러싸인 소스 코드에 소스의 블록 스타일 주석이있는 경우 기존 블록 주석의 * / 끝으로 인해 새 블록 주석이 유효하지 않아 컴파일 문제가 발생할 수 있습니다.

/* Block comment around whole function to keep it from getting used.
 * What's even the purpose of this function?
int myUnusedFunction(void)
{
    int i = 5;

    /* Return 5 */
    return i;
}
*/ 

이전 예에서 함수의 마지막 두 줄과 마지막 '* /'는 컴파일러에서 볼 수 있으므로 오류와 함께 컴파일됩니다. 더 안전한 방법은 차단하려는 코드 주위에 #if 0 지시문을 사용하는 것입니다.

#if 0
/* #if 0 evaluates to false, so everything between here and the #endif are
 * removed by the preprocessor. */
int myUnusedFunction(void)
{
    int i = 5;
    return i;
}
#endif

이점은 코드로 돌아가서 코드를 찾으려는 경우 모든 의견을 검색하는 것보다 "#if 0"검색이 훨씬 쉽습니다.

또 다른 중요한 이점은 #if 0 하여 코드를 주석 처리 할 수 ​​있다는 것입니다. 의견으로는이 작업을 수행 할 수 없습니다.

#if 0 을 사용하는 대신 # #defined 되지 않지만 코드가 차단되는 이유를보다 잘 설명하는 이름을 사용하는 것이 좋습니다. 예를 들어 쓸데없는 데드 코드로 보이는 함수가있는 경우 #if defined(POSSIBLE_DEAD_CODE) 또는 #if defined(FUTURE_CODE_REL_020201) 하여 다른 기능이 필요한 코드 나 비슷한 코드를 사용할 수 있습니다. 그런 다음 해당 소스를 제거하거나 활성화 할 때 해당 소스 섹션을 쉽게 찾을 수 있습니다.

토큰 붙여 넣기

토큰 붙여 넣기를 사용하면 두 개의 매크로 인수를 함께 붙일 수 있습니다. 예를 들어, front##backfrontback 합니다. 유명한 예가 Win32의 <TCHAR.H> 헤더입니다. 표준 C에서는 넓은 문자열을 선언하는 L"string" 을 쓸 수 있습니다. 그러나 Windows API는 하나의 단지로 넓은 문자열 좁은 문자열 사이에 변환 할 수 있습니다 #define 보내고 UNICODE . 문자열 리터럴을 구현하기 위해 TCHAR.H 는 이것을 사용합니다.

#ifdef UNICODE
#define TEXT(x) L##x
#endif

사용자가 TEXT("hello, world") 를 작성하고 UNICODE가 정의 될 때마다 C 사전 처리기는 L 과 매크로 인수를 연결합니다. L 와 연결 "hello, world" 제공 L"hello, world" .

사전 정의 된 매크로

사전 정의 된 매크로는 프로그램을 정의 할 필요없이 C 사전 프로세서가 이미 이해하고있는 매크로입니다. 예제에는 다음이 포함됩니다

필수 사전 정의 매크로

  • 현재 소스 파일 (문자열 리터럴)의 파일 이름을 제공하는 __FILE__
  • 현재 줄 번호 (정수 상수)에 대해 __LINE__ ,
  • __DATE__ 는 컴파일 날짜 (문자열 리터럴)이고,
  • __TIME__ 은 컴파일 타임 (문자열 리터럴)입니다.

__func__ (ISO / IEC 9899 : 2011 §6.4.2.2)와 관련된 미리 정의 된 식별자도 있는데 이것은 매크로가 아닙니다 .

__func__ 식별자는 각 함수 정의의 여는 중괄호 바로 다음에 선언 된 것처럼 번역자에 의해 암시 적으로 선언됩니다.

 static const char __func__[] = "function-name";

나타난다. 여기서 function-name 은 어휘 적으로 둘러싸는 함수의 이름이다.

__FILE__ , __LINE____func__ 은 특히 ​​디버깅 목적으로 유용합니다. 예 :

fprintf(stderr, "%s: %s: %d: Denominator is 0", __FILE__, __func__, __LINE__);

C99 이전의 컴파일러는 __func__ 지원할 수도 있고 지원하지 않을 수도 있고 다르게 이름이 지정된 것과 동일한 매크로를 가질 수도 있습니다. 예를 들어 gcc는 C89 모드에서 __FUNCTION__ 을 사용했습니다.

아래의 매크로들은 구현에 대한 세부 사항을 요구합니다 :

  • __STDC_VERSION__ 구현 된 C 표준의 버전. 이 포맷하여 일정한 정수 yyyymmL (값 201112L C11 값에 대한 199901L C99를 들어, 이는 C89 / C90에 정의되지 않은)
  • __STDC_HOSTED__ 호스트 된 구현 __STDC_HOSTED__ 1 , 그렇지 않으면 0 입니다.
  • __STDC__ 1 인 경우 구현은 C 표준을 준수합니다.

기타 사전 정의 된 매크로 (필수는 아닙니다)

ISO / IEC 9899 : 2011 §6.10.9.2 환경 매크로 :

  • __STDC_ISO_10646__ yyyymmL 형식의 정수 상수입니다 (예 : yyyymmL ). 이 기호가 정의되면 유니 코드 필수 집합의 모든 문자는 wchar_t 유형의 객체에 저장 될 때 해당 문자의 짧은 식별자와 동일한 값 wchar_t 집니다. Unicode 필수 집합은 ISO / IEC 10646에 정의 된 모든 문자와 지정된 수정 연도 및 기술 정오표로 구성됩니다. 다른 인코딩이 사용되면 매크로는 정의되지 않고 사용되는 실제 인코딩은 구현 정의됩니다.

  • __STDC_MB_MIGHT_NEQ_WC__ wchar_t 의 인코딩에서 기본 문자 집합의 멤버가 정수 문자 상수의 고독한 문자로 사용될 때 해당 값과 같을 필요가 없음을 나타 내기위한 정수 상수 1.

  • __STDC_UTF_16__ 의도 한 정수 상수는 입력 값 것을 나타내는 char16_t UTF-16 인코딩된다. 다른 인코딩이 사용되면 매크로는 정의되지 않고 사용되는 실제 인코딩은 구현 정의됩니다.

  • __STDC_UTF_32__ 의도 한 정수 상수는 입력 값 것을 나타내는 char32_t UTF-32 인코딩된다. 다른 인코딩이 사용되면 매크로는 정의되지 않고 사용되는 실제 인코딩은 구현 정의됩니다.

ISO / IEC 9899 : 2011 §6.10.8.3 조건부 기능 매크로

  • __STDC_ANALYZABLE__ 부속서 L (분석 가능성)의 규격에 부합 함을 나타 내기위한 정수 상수 1.
  • __STDC_IEC_559__ 부속서 F (IEC 60559 부동 소수점 산술)의 명세에 부합 함을 나타 내기위한 정수 상수 1.
  • __STDC_IEC_559_COMPLEX__ 부속서 G (IEC 60559 호환 복소 산술)의 규격 준수 여부를 나타내는 정수 상수 1.
  • __STDC_LIB_EXT1__ 부속서 K (경계 검사 인터페이스)에 정의 된 확장에 대한 지원을 표시하기위한 정수 상 201112L .
  • __STDC_NO_ATOMICS__ 구현이 원자 유형 ( _Atomic 유형 규정 자 포함) 및 <stdatomic.h> 헤더를 지원하지 않음을 나타 내기위한 정수 상수 1.
  • __STDC_NO_COMPLEX__ 구현이 복합 유형 또는 <complex.h> 헤더를 지원하지 않음을 나타 내기위한 정수 상수 1입니다.
  • __STDC_NO_THREADS__ 정수 상수 1. 구현이 <threads.h> 헤더를 지원하지 않음을 나타냅니다.
  • __STDC_NO_VLA__ 구현이 가변 길이 배열 또는 가변적으로 수정 된 유형을 지원하지 않음을 나타 내기위한 정수 상수 1입니다.

헤더 가드 포함

거의 모든 헤더 파일은 포함 경비 관용구를 따라야합니다.

my-header-file.h

#ifndef MY_HEADER_FILE_H
#define MY_HEADER_FILE_H

// Code body for header file

#endif

이렇게하면 여러 위치에서 #include "my-header-file.h"#include "my-header-file.h" 할 때 함수, 변수 등의 중복 선언이 발생하지 않습니다. 다음 파일 계층 구조를 상상해보십시오.

header-1.h

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

header-2.h

#include "header-1.h"

int myFunction2(MyStruct *value);

main.c

#include "header-1.h"
#include "header-2.h"

int main() {
    // do something
}

이 코드는 심각한 문제가 있습니다. MyStruct 의 자세한 내용이 두 번 정의 MyStruct 허용되지 않습니다. 하나의 헤더 파일에 다른 헤더 파일이 포함되어 있기 때문에 추적하기가 어려울 수있는 컴파일 오류가 발생합니다. 헤더 가드를 대신 사용했다면 :

header-1.h

#ifndef HEADER_1_H
#define HEADER_1_H

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

#endif

header-2.h

#ifndef HEADER_2_H
#define HEADER_2_H

#include "header-1.h"

int myFunction2(MyStruct *value);

#endif

main.c

#include "header-1.h"
#include "header-2.h"

int main() {
    // do something
}

그러면 다음과 같이 확장됩니다.

#ifndef HEADER_1_H
#define HEADER_1_H

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

#endif

#ifndef HEADER_2_H
#define HEADER_2_H

#ifndef HEADER_1_H // Safe, since HEADER_1_H was #define'd before.
#define HEADER_1_H

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

#endif

int myFunction2(MyStruct *value);

#endif

int main() {
    // do something
}

컴파일러가 header-1.h 의 두 번째 포함에 도달하면 HEADER_1_H 가 이미 이전 포함에 의해 정의되었습니다. Ergo, 그것은 다음과 같이 요약됩니다 :

#define HEADER_1_H

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

#define HEADER_2_H

int myFunction2(MyStruct *value);

int main() {
    // do something
}

따라서 컴파일 오류가 없습니다.

참고 : 헤더 가드의 이름을 지정하는 데는 여러 가지 규칙이 있습니다. 어떤 사람들은 이름을 HEADER_2_H_ 로 지정하고, 일부는 MY_PROJECT_HEADER_2_H 와 같은 프로젝트 이름을 포함합니다. 중요한 것은 팔로우 한 컨벤션이 프로젝트의 각 파일에 고유 한 헤더 가드가 있도록하는 것입니다.


구조체 세부 정보가 헤더에 포함되지 않은 경우 선언 된 유형은 불완전하거나 불투명 한 유형 입니다. 이러한 유형은 유용 할 수 있으며 함수의 사용자로부터 구현 세부 사항을 숨 깁니다. 많은 경우, 표준 C 라이브러리의 FILE 유형은 불투명 한 유형으로 간주 될 수 있습니다 (표준 I / O 기능의 매크로 구현이 구조의 내부를 사용할 수 있도록 일반적으로 불투명하지는 않지만). 이 경우, header-1.h 는 다음을 포함 할 수 있습니다.

#ifndef HEADER_1_H
#define HEADER_1_H

typedef struct MyStruct MyStruct;

int myFunction(MyStruct *value);

#endif

구조체에는 반드시 태그 이름 (여기서는 MyStruct - 태그 네임 스페이스에 있고 typedef 이름 MyStruct 의 일반 식별자 네임 스페이스와 별도로)이 있어야하며 { … } 는 생략해야합니다. 이것은 "구조 유형이 말한다 struct MyStruct 그것이 별칭이 MyStruct ".

구현 파일에서 유형의 완성을 위해 구조의 세부 사항을 정의 할 수 있습니다.

struct MyStruct {
    …
};

C11을 사용하는 경우 typedef struct MyStruct MyStruct; 반복 할 수 있습니다 typedef struct MyStruct MyStruct; 컴파일 오류를 일으키지 않고 선언을 할 수 있지만 이전 버전의 C는 불평 할 것이다. 결과적으로,이 예에서 C11을 지원하는 컴파일러 만 사용하여 코드가 컴파일 된 경우에도 포함 보호 관용구를 사용하는 것이 가장 좋습니다.


많은 컴파일러가 동일한 결과를 갖는 #pragma once 지시문을 지원합니다.

my-header-file.h

#pragma once

// Code for header file

그러나 #pragma once 는 C 표준의 일부가 아니므로 코드를 사용하면 코드가 덜 이식됩니다.


몇몇 헤더는 포함 경비 관용구를 사용하지 않습니다. 한 가지 구체적인 예는 표준 <assert.h> 헤더입니다. 단일 번역 단위에 여러 번 포함될 수 있으며 그 결과는 헤더가 포함될 때마다 매크로 NDEBUG 가 정의되는지 여부에 따라 달라집니다. 때로는 유사한 요구 사항이있을 수 있습니다. 그러한 경우는 거의 없을 것입니다. 일반적으로 헤더는 포함 경비 관용구로 보호해야합니다.

FOREACH 구현

또한 매크로를 사용하여 코드를 읽고 쓰는 것을 더 쉽게 만들 수 있습니다. 예를 들어, single-linked 및 doubly-linked리스트, 큐 등과 같은 일부 데이터 구조에 대해 C로 foreach 구조를 구현하기위한 매크로를 구현할 수 있습니다.

여기에 작은 예가 있습니다.

#include <stdio.h>
#include <stdlib.h>

struct LinkedListNode
{
    int data;
    struct LinkedListNode *next;
};

#define FOREACH_LIST(node, list) \
     for (node=list; node; node=node->next)

/* Usage */
int main(void)
{
    struct LinkedListNode *list, **plist = &list, *node;
    int i;

    for (i=0; i<10; i++)
    {
         *plist = malloc(sizeof(struct LinkedListNode));
         (*plist)->data = i;
         (*plist)->next = NULL;
         plist          = &(*plist)->next;
    }

    /* printing the elements here */
    FOREACH_LIST(node, list)
    {
        printf("%d\n", node->data);
    }
}

이러한 데이터 구조에 대한 표준 인터페이스를 만들고 FOREACH 의 일반적인 구현을 다음과 같이 작성할 수 있습니다.

#include <stdio.h>
#include <stdlib.h>

typedef struct CollectionItem_
{
    int data;
    struct CollectionItem_ *next;
} CollectionItem;

typedef struct Collection_
{
    /* interface functions */
    void* (*first)(void *coll);
    void* (*last) (void *coll);
    void* (*next) (void *coll, CollectionItem *currItem);

    CollectionItem *collectionHead;
    /* Other fields */
} Collection;

/* must implement */
void *first(void *coll)
{
    return ((Collection*)coll)->collectionHead;
}

/* must implement */
void *last(void *coll)
{
    return NULL;
}

/* must implement */
void *next(void *coll, CollectionItem *curr)
{
    return curr->next;
}

CollectionItem *new_CollectionItem(int data)
{
    CollectionItem *item = malloc(sizeof(CollectionItem));
    item->data = data;
    item->next = NULL;
    return item;
}

void Add_Collection(Collection *coll, int data)
{
    CollectionItem **item = &coll->collectionHead;
    while(*item)
        item = &(*item)->next;
    (*item) = new_CollectionItem(data);
}

Collection *new_Collection()
{
    Collection *nc = malloc(sizeof(Collection));
    nc->first = first;
    nc->last  = last;
    nc->next  = next;
    return nc;
}

/* generic implementation */
#define FOREACH(node, collection)                      \
    for (node  = (collection)->first(collection);      \
         node != (collection)->last(collection);       \
         node  = (collection)->next(collection, node))

int main(void)
{
    Collection *coll = new_Collection();
    CollectionItem *node;
    int i;

    for(i=0; i<10; i++)
    {
         Add_Collection(coll, i);
    }

    /* printing the elements here */
    FOREACH(node, coll)
    {
        printf("%d\n", node->data);
    }
}

이 일반 구현을 사용하려면 데이터 구조에 대해 이러한 함수를 구현하십시오.

1.  void* (*first)(void *coll);
2.  void* (*last) (void *coll);
3.  void* (*next) (void *coll, CollectionItem *currItem);

C ++로 컴파일 된 C ++ 코드에서 C 외부 사용에 대한 __cplusplus - name mangling

언어 차이로 인해 컴파일러가 C 컴파일러인지 또는 C ++ 컴파일러인지에 따라 포함 파일이 전처리 기와 다른 출력을 생성해야하는 경우가 있습니다.

예를 들어 함수 또는 다른 외부 함수는 C 소스 파일에 정의되어 있지만 C ++ 소스 파일에 사용됩니다. C ++에서는 함수 인수 유형을 기반으로 고유 한 함수 이름을 생성하기 위해 이름 변환 (또는 이름 데코레이션)을 사용하기 때문에 C ++ 소스 파일에서 사용되는 C 함수 선언은 링크 오류를 유발합니다. C ++ 컴파일러는 C ++ 용 이름 변환 규칙을 사용하여 컴파일러 출력의 지정된 외부 이름을 수정합니다. 결과는 C ++ 컴파일러 출력이 C 컴파일러 출력과 링크되어있을 때 외부에서 발견되지 않은 링크 오류입니다.

C 컴파일러는 이름 맹 글링을하지 않지만 C ++ 컴파일러는 C ++ 컴파일러에서 생성 된 모든 외부 레이블 (함수 이름 또는 변수 이름)에 대해 수행하므로 사전 정의 된 선행 처리기 매크로 인 __cplusplus 가 도입되어 컴파일러 탐지가 가능합니다.

C 및 C ++ 사이의 외부 이름에 대한 호환되지 않는 컴파일러 출력 문제를 해결하기 위해 __cplusplus 매크로는 C ++ 전 처리기에 정의되어 있으며 C 전 처리기에 정의되어 있지 않습니다. 이 매크로 이름은 조건부 전 처리기 #ifdef 지시문 또는 defined() 연산자와 함께 #if 와 함께 사용하여 소스 코드 또는 포함 파일이 C ++ 또는 C로 컴파일되는지 여부를 알 수 있습니다.

#ifdef __cplusplus
printf("C++\n");
#else
printf("C\n");
#endif

또는 사용할 수 있습니다.

#if defined(__cplusplus)
printf("C++\n");
#else
printf("C\n");
#endif

C ++ 소스 파일에서 사용되는 C 컴파일러로 컴파일 된 C 소스 파일에서 함수의 올바른 함수 이름을 지정하려면 __cplusplus 정의 상수를 확인하여 extern "C" { /* ... */ }; 헤더 파일이 C ++ 소스 파일에 포함될 때 C 외부를 선언하는 데 사용됩니다. 그러나 C 컴파일러로 컴파일 할 때 extern "C" { */ ... */ }; 사용되지 않습니다. 이 조건부 컴파일은 extern "C" { /* ... */ }; C ++에서는 유효하지만 C에서는 유효하지 않습니다.

#ifdef __cplusplus
// if we are being compiled with a C++ compiler then declare the
// following functions as C functions to prevent name mangling.
extern "C" {
#endif

// exported C function list.
int foo (void);

#ifdef __cplusplus
// if this is a C++ compiler, we need to close off the extern declaration.
};
#endif

함수와 같은 매크로

함수와 유사한 매크로는 inline 함수와 비슷하지만 임시 디버그 로그와 같은 일부 경우에 유용합니다.

#ifdef DEBUG
# define LOGFILENAME "/tmp/logfile.log"

# define LOG(str) do {                            \
  FILE *fp = fopen(LOGFILENAME, "a");            \
  if (fp) {                                       \
    fprintf(fp, "%s:%d %s\n", __FILE__, __LINE__, \
                 /* don't print null pointer */   \
                 str ?str :"<null>");             \
    fclose(fp);                                   \
  }                                               \
  else {                                          \
    perror("Opening '" LOGFILENAME "' failed");   \
  }                                               \
} while (0)
#else
  /* Make it a NOOP if DEBUG is not defined. */
# define LOG(LINE) (void)0
#endif


#include <stdio.h>

int main(int argc, char* argv[])
{
    if (argc > 1)
        LOG("There are command line arguments");
    else
        LOG("No command line arguments");
    return 0;
}

두 경우 모두 ( DEBUG 있거나없는 경우) 호출은 void 반환 유형이있는 함수와 같은 방식으로 작동합니다. 이렇게하면 if/else 조건문이 예상대로 해석됩니다.

DEBUG 경우 이것은 do { ... } while(0) 구조를 통해 구현됩니다. 다른 경우, (void)0 은 무시되는 부작용이없는 명령문입니다.

후자에 대한 대안은

#define LOG(LINE) do { /* empty */ } while (0)

모든 경우에 구문 적으로 첫 번째 구문과 동일합니다.

- 당신은 GCC를 사용하는 경우, 당신은 또한 표준이 아닌 GNU 확장 사용하여 결과를 반환하는 함수와 같은 매크로 구현할 수 문 식을 . 예 :

#include <stdio.h>

#define POW(X, Y) \
({ \
        int i, r = 1; \
        for (i = 0; i < Y; ++i) \
                r *= X; \
        r; \ // returned value is result of last operation
})

int main(void)
{
        int result;

        result = POW(2, 3); 
        printf("Result: %d\n", result);
}

Variadic 인수 매크로

C99

가변 인수가있는 매크로 :

코드 디버깅을 위해 print 매크로를 만들고 싶다면 다음 매크로를 예로 들어 보겠습니다.

#define debug_print(msg) printf("%s:%d %s", __FILE__, __LINE__, msg)

사용법의 몇 가지 예 :

somefunc() 함수는 실패하면 -1을 반환하고 성공하면 0을 반환하며 코드 내의 여러 다른 위치에서 호출됩니다.

int retVal = somefunc();

if(retVal == -1)
{
    debug_printf("somefunc() has failed");
}

/* some other code */

 retVal = somefunc();

if(retVal == -1)
{
    debug_printf("somefunc() has failed");
}

somefunc() 의 구현이 변경되면 어떻게됩니까? 그리고 가능한 다른 오류 유형과 일치하는 다른 값을 반환합니다. 여전히 디버그 매크로를 사용하고 오류 값을 인쇄하려고합니다.

debug_printf(retVal);      /* this would obviously fail */
debug_printf("%d",retVal); /* this would also fail */

이 문제를 해결하기 위해 __VA_ARGS__ 매크로가 도입되었습니다. 이 매크로는 여러 매개 변수 X- 매크로를 허용합니다.

예:

 #define debug_print(msg, ...) printf(msg, __VA_ARGS__) \
                               printf("\nError occurred in file:line (%s:%d)\n", __FILE__, __LINE)

용법:

int retVal = somefunc();

debug_print("retVal of somefunc() is-> %d", retVal);

이 매크로를 사용하면 여러 매개 변수를 전달하고 인쇄 할 수 있지만 매개 변수를 보내지 못하게합니다.

debug_print("Hey");

매크로가 적어도 하나 이상의 인수를 예상하고 전처리 debug_print() 매크로에서 쉼표가 부족하다는 것을 무시하지 않으므로 구문 오류가 발생합니다. 또한 debug_print("Hey",); 매크로에 전달 된 인수를 비워 둘 수 없으므로 구문 오류가 발생합니다.

이를 해결하기 위해 ##__VA_ARGS__ 매크로가 도입되었습니다.이 매크로는 가변 인수가 없으면 코드에서 전처리 ##__VA_ARGS__ 의해 쉼표가 삭제된다는 것을 나타냅니다.

예:

 #define debug_print(msg, ...) printf(msg, ##__VA_ARGS__) \
                               printf("\nError occured in file:line (%s:%d)\n", __FILE__, __LINE)

용법:

 debug_print("Ret val of somefunc()?");
 debug_print("%d",somefunc());


Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow