C의 매크로와 기능
기능을 사용하는 것보다 매크로를 사용하는 것이 더 좋은 경우를 자주 볼 수 있습니다.
기능에 비해 매크로의 단점을 예를 들어 설명해 주실 수 있습니까?
매크로는 텍스트 치환에 의존하며 유형 검사를 수행하지 않기 때문에 오류가 발생하기 쉽습니다.예를 들어 다음과 같은 매크로가 있습니다.
#define square(a) a * a
다음 정수와 함께 사용하면 정상적으로 동작합니다.
square(5) --> 5 * 5 --> 25
표현과 함께 사용하면 매우 이상한 행동을 합니다.
square(1 + 2) --> 1 + 2 * 1 + 2 --> 1 + 2 + 2 --> 5
square(x++) --> x++ * x++ --> increments x twice
논거에 괄호를 두르는 것은 도움이 되지만 이러한 문제를 완전히 제거하지는 않습니다.
매크로에 여러 개의 문이 포함되어 있는 경우 control-flow 구조에 문제가 발생할 수 있습니다.
#define swap(x, y) t = x; x = y; y = t;
if (x < y) swap(x, y); -->
if (x < y) t = x; x = y; y = t; --> if (x < y) { t = x; } x = y; y = t;
이 문제를 해결하기 위한 일반적인 전략은 문장을 "do {..." 안에 넣는 것입니다.(0)" 루프 중 }.
이름이 같지만 시멘틱스가 다른 필드를 포함하는 구조가 2개 있는 경우, 같은 매크로가 양쪽에서 동작하여 이상한 결과를 얻을 수 있습니다.
struct shirt
{
int numButtons;
};
struct webpage
{
int numButtons;
};
#define num_button_holes(shirt) ((shirt).numButtons * 4)
struct webpage page;
page.numButtons = 2;
num_button_holes(page) -> 8
마지막으로 매크로 디버깅이 어려워질 수 있습니다.다음 예시와 같이 디버깅은 매크로를 통과할 수 없기 때문에 이해하기 위해 확장해야 하는 이상한 구문 오류 또는 런타임 오류가 발생할 수 있습니다(gcc - E 등).
#define print(x, y) printf(x y) /* accidentally forgot comma */
print("foo %s", "bar") /* prints "foo %sbar" */
인라인 함수 및 상수는 매크로에서 발생하는 이러한 문제의 대부분을 방지하는 데 도움이 되지만 항상 해당되지는 않습니다.매크로가 의도적으로 다형 동작을 지정하기 위해 사용되는 경우 의도하지 않은 다형성을 피하기 어려울 수 있습니다.C++에는 매크로를 사용하지 않고 복잡한 다형 구조를 쉽게 작성할 수 있는 템플릿 등의 기능이 다수 있습니다.자세한 내용은 Strustrup의 C++ 프로그래밍 언어를 참조하십시오.
매크로 기능:
- 매크로가 사전 처리됨
- 유형 확인 없음
- 코드 길이 증가
- 매크로를 사용하면 부작용이 발생할 수 있습니다.
- 실행 속도 향상
- 컴파일 매크로 이름이 매크로 값으로 대체되기 전에
- 작은 코드가 여러 번 표시될 때 유용합니다.
- 매크로가 컴파일 오류를 체크하지 않음
기능 특징:
- 함수가 컴파일되었습니다.
- 유형 확인이 완료되었습니다.
- 코드 길이는 그대로입니다.
- 부작용 없음
- 실행 속도가 느리다
- 함수 호출 중에 제어 전환이 수행됩니다.
- 큰 코드가 여러 번 표시될 때 유용합니다.
- 함수 체크 컴파일 오류
부작용은 매우 크다.일반적인 예를 다음에 제시하겠습니다.
#define min(a, b) (a < b ? a : b)
min(x++, y)
다음과 같이 확장됩니다.
(x++ < y ? x++ : y)
x
같은 스테이트먼트에서2배로 증가합니다.(및 정의되지 않은 동작)
여러 줄의 매크로를 작성하는 것도 귀찮은 일입니다.
#define foo(a,b,c) \
a += 10; \
b += 10; \
c += 10;
은 '이 명령어'가 필요합니다.\
각 행의 끝에 있습니다.
매크로에서는, 1개의 표현식을 사용하지 않는 한, 아무것도 「반환」 수 없습니다.
int foo(int *a, int *b){
side_effect0();
side_effect1();
return a[0] + b[0];
}
GCC의 문 식을 사용하지 않는 한 매크로에서 이 작업을 수행할 수 없습니다. (EDIT: 쉼표 연산자를 사용할 수 있지만...간과했다다만, 아직 읽기 어려운 경우가 있습니다.)
업무 순서: (@ouah의 서비스)
#define min(a,b) (a < b ? a : b)
min(x & 0xFF, 42)
다음과 같이 확장됩니다.
(x & 0xFF < 42 ? x & 0xFF : 42)
★★★★★★★★★★★★★★★★★.&
보다 낮다<
0xFF < 42
먼저 평가됩니다.
의심스러운 경우 함수(또는 인라인 함수)를 사용합니다.
그러나 여기의 답변들은 매크로가 어리석은 사고가 일어날 수 있기 때문에 악하다는 단순한 견해를 갖는 대신 매크로의 문제점들을 대부분 설명하고 있다.
당신은 함정에 대해 알고 그것들을 피하는 법을 배울 수 있다.매크로를 사용하는 것은 타당한 이유가 있는 경우 뿐입니다.
매크로를 사용하면 다음과 같은 이점이 있는 예외적인 경우가 있습니다.
- 범용 함수는 다음과 같이 다양한 유형의 입력 인수에 사용할 수 있는 매크로를 가질 수 있습니다.
- 의 C를 에 매핑할 수 .
va_args
.
예: https://stackoverflow.com/a/24837037/432509 - 옵션으로 디버깅 문자열과 같은 로컬 정보를 포함할 수 있습니다.
)__FILE__
,__LINE__
,__func__
상태를 합니다.) 의의의 / 사사사 ). ). 。assert
(디버깅 빌드에 도움이 되는 코드)를 잘못 사용하여 컴파일하지 않도록 합니다. - 체크 할 수 . 입력 Arg의 유형, 크기, 확인 등의 테스트를 수행할 수 있습니다.
struct
전에 출석하다
(다형 타입에 도움이 됩니다).
또는 어레이가 길이 조건을 충족하는지 확인합니다.
참조: https://stackoverflow.com/a/29926435/432509 - 함수가 타입 체크를 하는 것에 주목하고 있습니다만, C는 값(ints/floats 등)도 강제합니다.드문 경우지만 이 문제가 발생할 수 있습니다.입력 Arg에 대한 함수보다 더 엄격한 매크로를 작성할 수 있습니다.참조: https://stackoverflow.com/a/25988779/432509
- 기능의 래퍼로서 사용하는 경우, 예를 들면, 반복을 피하고 싶은 경우가 있습니다.
func(FOO, "FOO");
문자열을 할 수 있습니다.func_wrapper(FOO);
- 발신자 로컬스코프에서 변수를 조작하는 경우 포인터에 포인터를 전달하면 정상적으로 동작하지만 매크로를 사용하는 번거로움이 줄어들 수 있습니다.
(패키지 단위 연산의 경우 여러 변수에 대한 수정은 함수보다 매크로를 선호할 수 있는 예입니다. 함수는 옵션일 수 있기 때문에 컨텍스트에 따라 크게 달라집니다).
물론 이들 중 일부는 표준 C가 아닌 컴파일러 확장에 의존합니다., 휴대 코드가 , 「」가 필요하게 경우가 있습니다.ifdef
컴파일러가 지원하는 경우에만 이용할 수 있습니다.
다중 인수 인스턴스화 방지
매크로에서 오류가 발생하는 가장 일반적인 원인 중 하나이므로 주의해 주십시오(예를 들어 매크로가 여러 번 증가할 수 있는 경우).
복수의 인수의 인스턴스화에 의한 부작용을 회피하는 매크로를 작성할 수 있습니다.
C11 범용
만약 당신이 갖고 싶다면square
다양한 타입으로 동작하며 C11을 지원하는 매크로를 사용할 수 있습니다.이 작업을 수행할 수 있습니다.
inline float _square_fl(float a) { return a * a; }
inline double _square_dbl(float a) { return a * a; }
inline int _square_i(int a) { return a * a; }
inline unsigned int _square_ui(unsigned int a) { return a * a; }
inline short _square_s(short a) { return a * a; }
inline unsigned short _square_us(unsigned short a) { return a * a; }
/* ... long, char ... etc */
#define square(a) \
_Generic((a), \
float: _square_fl(a), \
double: _square_dbl(a), \
int: _square_i(a), \
unsigned int: _square_ui(a), \
short: _square_s(a), \
unsigned short: _square_us(a))
스테이트먼트 식
이는 GCC, Clang, EKOPath 및 인텔 C++(MSVC는 지원하지 않음)에서 지원되는 컴파일러 확장입니다.
#define square(a_) __extension__ ({ \
typeof(a_) a = (a_); \
(a * a); })
따라서 매크로의 단점은 우선 이러한 매크로를 사용해야 하며, 이러한 매크로가 널리 지원되지 않는다는 것입니다.
장점은 이 수 있다는 장점이 있습니다.square
하다
예 1:
#define SQUARE(x) ((x)*(x))
int main() {
int x = 2;
int y = SQUARE(x++); // Undefined behavior even though it doesn't look
// like it here
return 0;
}
반면:
int square(int x) {
return x * x;
}
int main() {
int x = 2;
int y = square(x++); // fine
return 0;
}
예 2:
struct foo {
int bar;
};
#define GET_BAR(f) ((f)->bar)
int main() {
struct foo f;
int a = GET_BAR(&f); // fine
int b = GET_BAR(&a); // error, but the message won't make much sense unless you
// know what the macro does
return 0;
}
비교:
struct foo {
int bar;
};
int get_bar(struct foo *f) {
return f->bar;
}
int main() {
struct foo f;
int a = get_bar(&f); // fine
int b = get_bar(&a); // error, but compiler complains about passing int* where
// struct foo* should be given
return 0;
}
위의 답변에서는 매우 중요하다고 생각하는 매크로에 비해 기능의 장점을 깨닫지 못했습니다.
함수는 인수로 전달할 수 있지만 매크로는 전달할 수 없습니다.
구체적인 예:다른 문자열 내에서 검색할 문자의 명시적 목록이 아니라 일부 테스트(사용자 정의)를 통과한 문자가 발견될 때까지 0을 반환하는 (a에 포인터) 함수를 받아들이는 표준 'strpbrk' 함수의 대체 버전을 작성하려고 합니다. 이를 실행하는 이유 중 하나는 다른 표준 라이브러리 함수를 이용할 수 있도록 하기 위해서입니다.즉, 구두점을 가득 채운 명시적인 문자열을 지정하는 대신 ctype을 전달할 수 있습니다.대신 h의 '없음'을 사용합니다. 만약 'ispect'가 매크로로만 구현되었다면, 이것은 작동하지 않을 것이다.
그 밖에도 많은 예가 있습니다.예를 들어, 비교가 함수가 아닌 매크로로 이루어진 경우 stdlib에 전달할 수 없습니다.h의 'qsort'입니다.
Python의 유사한 상황은 버전 2와 버전 3의 '인쇄'(non-passable statement vs. passable function)입니다.
파라미터 및 코드의 유형 확인은 반복되지 않으며, 이로 인해 코드가 팽창할 수 있습니다.매크로 구문은 세미콜론이나 우선순위가 방해가 되는 이상한 엣지 케이스도 얼마든지 발생시킬 수 있습니다.여기 몇 가지 거시적 악을 보여주는 링크가 있습니다.
함수는 유형 확인을 수행합니다.이렇게 하면 안전성이 한층 높아집니다.
함수를 매크로 인수로 전달하면 매번 평가됩니다.예를 들어, 가장 일반적인 매크로 중 하나를 호출하는 경우:
#define MIN(a,b) ((a)<(b) ? (a) : (b))
이런 거죠.
int min = MIN(functionThatTakeLongTime(1),functionThatTakeLongTime(2));
기능.ThatTakeLongTime은 퍼포먼스를 크게 떨어뜨릴 수 있는5회 평가됩니다
이 답변에 추가...
매크로가 프리프로세서에 의해 프로그램에 직접 대체됩니다(기본적으로 프리프로세서 지시사항이기 때문입니다).따라서 필연적으로 각각의 기능보다 더 많은 메모리 공간을 사용합니다.한편 함수는 호출 및 결과 반환에 더 많은 시간이 필요하며 매크로를 사용하면 이러한 오버헤드를 방지할 수 있습니다.
또, 매크로에는, 다른 플랫폼에서의 프로그램 이식성에 도움이 되는 몇개의 특별한 툴이 있습니다.
매크로에는 함수와는 대조적으로 인수에 데이터 유형을 할당할 필요가 없습니다.
전반적으로 프로그래밍에 유용한 도구입니다.그리고 상황에 따라 매크로 명령과 기능을 모두 사용할 수 있습니다.
매크로의 한 가지 단점은 디버거가 확장 매크로를 가지고 있지 않은 소스 코드를 읽는다는 것입니다.따라서 매크로에서 디버거를 실행하는 것이 반드시 유용한 것은 아닙니다.말할 필요도 없이 매크로 내에서는 함수와 같이 중단점을 설정할 수 없습니다.
언급URL : https://stackoverflow.com/questions/9104568/macro-vs-function-in-c
'programing' 카테고리의 다른 글
마운트된 후크 함수로 Viewer.js를 초기화합니다.(오류:첫 번째 인수는 필수이며 요소여야 합니다.) (0) | 2022.08.08 |
---|---|
비동기 콜용 JNI 인터페이스 포인터(JNIEnv *)를 얻는 방법 (0) | 2022.08.08 |
조각이 있는 Android 검색 (0) | 2022.08.08 |
Vuex 비활성 mapGetters(인수가 전달됨) (0) | 2022.08.08 |
img src에 대해 Vue JS 데이터 바인딩이 작동하지 않음 (0) | 2022.08.08 |