C Language
전처리 기와 매크로
수색…
소개
모든 선행 처리기 명령은 해시 (파운드) 기호 #
합니다. 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
대한 첫 번째 호출에서 정의에서 이름 A
가 b
로 대체되고 확장 된 텍스트가 호출 위치에 놓입니다. 이 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##back
은 frontback
합니다. 유명한 예가 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 인수 매크로
가변 인수가있는 매크로 :
코드 디버깅을 위해 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());