매크로
컴파일러가 컴파일을 시작하기 전에 #define으로 정의된 상수를 매크로 확장 문자열로 변환하여 사용하는 것
- 프로그램의 가독성이 높아지고, 유지보수가 용이해진다.
- 매크로 확장 문자열에는 공백을 포함할 수 없다.
- #define은 전처리 과정에서 치환되므로 메모리 공간에 할당되지 않는다. ▶ 주소 접근이 불가능하다.
매크로 상수
- 매크로 상수는 다음과 같이 정의한다.
#define 매크로이름 값 // 전처리기 문의 끝에는 ;(세미콜론)을 붙이지 않는다.
매크로 상수 예제
#include <iostream>
#define PI 3.14159 // 매크로 상수 정의
using namespace std;
int main() {
double radius = 5;
cout << "PI: " << PI << endl;
cout << "원의 넓이: " << PI * radius << endl;
return 0;
}
PI: 3.14159
원의 넓이: 15.708
- 3.14159를 PI라는 식별자로 변환하여 사용하였다.
#ifdef
- 매크로가 정의되어 있는가 아닌가에 따라서 코드를 포함할지 말지 결정하는 전처리기 명령 ▶ 조건부 컴파일
- #ifdef와 #endif 사이에 코드를 작성한다.
- define 되는 것이 무엇이냐에 따라 간단히 무엇을 컴파일할 것인지를 나타낼 때 유용히 사용된다.
#include <iostream>
#define A
using namespace std;
int main() {
#ifdef A // A가 정의되었으므로 아래의 코드를 포함
cout << "Define_A" ;
#endif
#ifdef B // B는 정의되지 않으므로 아래의 코드를 포함하지 않음
cout << "Define_B";
#endif
return 0;
}
Define_A
- #else를 사용할 수 있다.
#include <iostream>
#define B
using namespace std;
int main() {
#ifdef A
cout << "Define_A";
#else
cout << "Define_B";
#endif
return 0;
}
Define_B
매크로 함수
- 매크로를 이용하여 #define에 인수로 함수의 정의를 전달함으로써, 함수처럼 동작하는 매크로 함수를 만들 수 있다.
- 일반함수와 달리 단순 치환만 하기 때문에 일반 함수와 완벽히 똑같이 동작하지는 않는다. ▶ 인수 타입 신경 X
- 매크로 함수를 사용하여 여러 개의 명령문을 동시에 포함할 수 있다.
- 함수 호출에 의한 성능 저하가 발생하지 않고, 프로그램 실행속도가 빨라진다.
- 정확한 함수의 구현이 어려운 탓에 디버깅이 어렵고, 함수 크기가 클수록 가독성이 떨어진다는 단점이 있다. ▶ 따라서 크기가 비교적 작은 간단한 함수를 대체하는 데 사용하는 것이 좋다.
매크로 함수 만들기
- 매크로 함수는 다음과 같이 정의한다.
#define 함수명(인자) 치환식
Ex) 제곱 함수
#include <iostream>
#define square(x) x * x // 매크로 함수 정의
using namespace std;
int main() {
cout << square(3) << endl;
return 0;
}
9
- square라는 이름의 매크로 함수를 생성하였고, 인자로는 x를 받는다. 그리고 인자 x를 x*x로 치환한다.
- 여기서 매크로 함수는 컴파일 되기 전에 전처리기에 의해 3*3으로 치환된다.
매크로 함수 유의점
#include <iostream>
#define square(x) x * x // 매크로 함수 정의
using namespace std;
int main() {
cout << square(3 + 1) << endl;
return 0;
}
7
- square 함수는 제곱 기능을 하기 때문에 3 + 1 = 4의 제곱인 16이 나올 것 같았지만 7이 나왔다.
- 이는 전처리기에서 square(3 + 1)이 다음과 같이 치환되었기 때문이다.
x * x = 3 + 1 * 3 + 1 = 3 + 3 + 1 = 7
- 3 + 1을 계산하기도 전에 x = 3 + 1로 치환하였기 때문에 7이라는 결과가 나타났다.
- 이런 경우에는 다음과 같이 바꿔 주어야 한다.
올바른 치환식
#include <iostream>
#define square(x) (x) * (x)
using namespace std;
int main() {
cout << square(3 + 1) << endl;
return 0;
}
16
- 이 경우에는 전처리기에서 치환할 때, (3 + 1) * (3 + 1)로 처리되기 때문에 정답이 16이 나왔다.
전처리 지시자를 활용한 매크로 함수
Ex) #을 이용한 매크로 함수
#include <iostream>
#define printString(str) cout << #str;
using namespace std;
int main() {
printString(Hello);
return 0;
}
Hello
- #은 어떠한 인자 앞에 붙으면 해당 인자를 문자열로 바꾸어 버린다.
Ex) ##을 이용한 매크로 함수
#include <iostream>
#define Comb(a, b) a ## b;
using namespace std;
int main() {
int Comb(x, y);
xy = 3;
cout << xy << endl;
return 0;
}
3
- ##은 받은 인자들을 하나로 합치는 기능을 수행한다.
- 위 코드에서는 x와 y를 합쳐 xy의 이름으로 int 자료형을 생성하였다.
인라인 함수
- 호출될 때 일반적인 함수의 호출 과정을 거치지 않고, 함수의 모든 코드를 호출된 자리에 바로 삽입하는 방식의 함수
- 이 방식을 이용하면 함수 호출 시간은 절약되나, 함수 호출 과정으로 생기는 여러 이점을 포기해야 한다.
- 코드가 매우 적은 함수만을 인라인 함수로 선언하는 것이 좋음
- 매크로 함수에서 소괄호를 적절히 사용하지 않아 발생하는 오류가 디버깅을 어렵게 만들었고, 이를 해결하기 위해 인라인(inline) 함수가 등장하였음.
- 인라인 함수는 전처리기가 치환하는 매크로 함수와 달리 그보다 똑똑한 컴파일러가 알아서 보통의 함수처럼 연산 순서를 고려하여 치환한다.
- 만약 이미 존재하는 함수와 이름이 같은 인라인 함수를 정의할 경우, 작업 비용이 적은 함수로 골라서 사용한다. 만약 인라인 함수의 비용이 더 클 경우 인라인 함수 구문은 무시된다.
인라인 함수 만들기
- 인라인 함수는 다음과 같이 정의한다.
__inline datatype function명(인자) {/*실행구문*/;}
// 예시
__inline int square(int x) { return x * x; }
Ex) 제곱 함수
#include <iostream>
using namespace std;
__inline int square(int x) { return x * x; } // 인라인 함수 정의
int main() {
cout << square(3) << endl;
return 0;
}
9
- 만약 main에서 square 함수를 정의하였다면 함수가 저장된 메모리로 점프하고, 해당 함수의 결과를 반환하는 과정에서 시간이 오래 걸리게 된다.
- 인라인 함수는 그러한 시간 소요를 줄이고자 아래와 같은 동작을 하도록 만든 기능이다.
cout << 3 * 3 << endl;
Ex) 인라인 함수는 컴파일러가 알아서 연산 순서를 처리
#include <iostream>
using namespace std;
__inline int square(int x) { return x * x; }
int main() {
cout << square(3 + 1) << endl;
return 0;
}
16
- 인라인 함수는 매크로 함수처럼 () 소괄호를 고려할 필요 없이 컴파일러가 알아서 연산을 보통의 함수처럼 처리한다.