전처리기
전처리기는 #으로 시작하는 예약어를 뜻합니다. 전처리기는 조건에 따라 복사 붙여넣기 된 코드를 출력합니다. 일반적으로 전처리기가 끝난 코드는 (#include로 인해서) 수백, 수천, 수만줄에 이르기도 합니다. 일반적으로 컴파일러는 전처리 후 파일을 생성하지 않습니다. 이는 컴파일 옵션을 조절하여 강제로 생성하게 할 수 있습니다.
gcc -E source.c -o output.i
- #include : 대상을 붙여넣기 합니다.
- #define : 대상을 정의합니다.
- #ifdef, #ifndef(if not define) : 이미 정의되었는지 여부를 판단합니다.
- #error : 강제로 에러를 발생시킵니다.
#include
C언어는 code파일(.c 또는 .cpp)파일과 header파일(.h;헤더)을 쌍(pair)으로 준비합니다. c파일에는 코드의 내용이, header파일에는 개요가(함수/변수 등의 선언) 포함하고 있습니다.
#include <stdio.h> // 등록된 라이브러리 폴더의 위치들 중에서 검색 #include "myHeader.h" // 현재 폴더를 기준으로 #include "../lib/yourHeader.h" // 상대 경로 지정
gcc -I /path/to/include/dir source.c -o output
문제는 어떤 header는 여러 파일에서 참조되면서 의도하지 않게 중복 될 수 있다는 것입니다. 중복 선언되게 되면 컴파일러는 잘못된 코드로 인식하여 에러를 출력합니다. 만약 다음과 같이 선언된다면, (어느 파일에서 최초로 호출 될 지는 알 수 없지만,) 최초로 호출되었을 때, __STDIO_H__가 정의되고 이후 정의될 때는 코드 전체가 스킵되는 효과를 볼 수 있을 것입니다.
#ifndef __STDIO_H__ // 만약 인용된 적이 없다면 #define __STDIO_H__ // 지금 인용 ... // 내용 #endif
#define
#define GREETING "good morning"
printf("greeting : %s\n", GREETING); // greeting : good morning
#define var(x) x + 1
printf("var(3) : %d\n", var(3)); // XXX(3) : 4
#define MIN(x, y) (x > y ? y : x)
printf("MIN(3,4): %d\n", MIN(3, 4)); // MIN(3,4): 3
#define log(x) printf("LOG(%d)\t: %s", __LINE__, x)
log("매크로 출력\n"); // LOG(17) : 매크로 출력
일부 매크로macro는 시스템에 미리 정의되어 바로 사용할 수 있습니다.
메크로 | 대체어 |
__FILE__ | 현재 파일명 |
__LINE__ | 현재 라인 |
__DATE__ | 시스템 날자 |
__TIME__ | 시스템 시각 |
__func__ | 현재 함수 |
#define out(x) printf(#x " = %d\n", x)
out(constVar); // constVar = 1
#define Aout(x, n) printf(#x "[%d] = %d\n", n, x[n])
for (int i = 0; i < 5; i++) {
a[i] = i;
Aout(a, i); // a[0] = 0, a[1] = 1 ...
}
volatile
컴파일 옵션으로 최적화를 수행 할 수 있습니다. 다만, 기본 옵션은 최적화를 수행하지 않습니다.
gcc -O1 source.c -o output
- O0: 최적화를 수행하지 않습니다. (Default)
- O1: 최소한의 최적화를 수행합니다. 코드 크기는 작아지지만 실행 속도는 느려집니다.
- O2: 중간 수준의 최적화를 수행합니다. 실행 속도와 코드 크기가 모두 개선됩니다.
- O3: 최대 수준의 최적화를 수행합니다. 실행 속도와 코드 크기가 모두 개선되지만, 컴파일 시간이 더 오래 걸릴 수 있습니다.
- Os: 코드 크기 최적화를 수행합니다. 실행 속도는 느려질 수 있습니다.
- Ofast: 최적화를 가능한 한 많이 수행합니다. 다른 최적화 옵션보다 더 많은 최적화를 수행하며, 컴파일러가 안전성에 대한 검사를 줄일 수 있습니다.
volatile 키워드는 강제적으로 최적화를 수행하지 않도록 지정합니다. 아래 코드는 임베디드 센서를 읽는 코드로 sensor의 값이 로직과 관계없이 변경 될 수 있습니다.
int main() { volatile int sensor = 1; printf("Sensor : %d\n", sensor); printf("Sensor : %d\n", sensor); printf("Sensor : %d\n", sensor); }
extern
외부의 파일을 추가 할 때, 컴파일러에게 라이브러리가 추가 될 예정임을 알려주는 예약어입니다.
extern void fnStaticLib();
라이브러리 파일
운영체제 | 에디터/컴파일러 | 파일형식 |
Windows | Visual Studio 2022 ... | .lib |
linux | VSCode, gcc | .a |
- 다수의 프로그램에서 참조될 수 있고, 메모리를 절약할 수 있습니다.
- 코드를 공개를 피할 수 있습니다.
- 라이브러리 배포가 쉽습니다.
Visual Studio에서 .lib파일 생성하기
VScode에서 .a파일 생성하기
gcc -c StaticLib.cpp
ar rcs StaticLib.a StaticLib.o
gcc -c 7_04_Extern.cpp -o 7_04_Extern.o
gcc -o 7_04_Extern 7_04_Extern.o StaticLib.a
gcc -o 7_04_Extern 7_04_Extern.cpp StaticLib.a
7_04_Extern.exe
inline
프로그램 수행 중 함수가 호출되면, 함수가 할당된 영역으로 이동 후에 함수 기능이 완료되면 호출위치로 돌아옵니다.
이렇게 구성하면 함수가 1개만 존재하면 됩니다.
inline 예약어가 선언되면, 함수가 재사용되지 않습니다.
호출되는 위치에 함수의 내용이 직접 기술되기 때문에 메모리를 손해를 보지만, 성능을 개선 할 수 있습니다.
inline int add(int a, int b) { return a + b; }
Math
math.h를 #include하면 수학함수를 추가 할 수 있습니다.
- abs : 절대값을 반환합니다.
- sqrt : 제곱근을 반환합니다.
- sin, cos ,tan : 삼각함수를 반환합니다.
- asin, acos, atan : 역삼각함수를 반환합니다.
- pow : 지수함수를 반환합니다.
- exp : 밑이 e인 지수함수를 반환합니다.
- log, log2, log10 : 로그 함수를 반환합니다. 숫자는 밑의 크기를 의미합니다.
- round, ceil, floor : 반올림, 올림, 내림을 반환합니다.
random
stdlib.h을 #include하면 rand함수를 추가 할 수 있습니다.
rand();
Malloc
malloc함수는 지정한 크기의 메모리를 동적으로 할당한 뒤, void *로 반환합니다.
pArr = (char*)malloc(szArr);
File
..._s는 안정성을 추가한 함수입니다.(권장)
FILE *pFile; char Buf[100]; // 파일을 엽니다. fopen_s(&pFile, "log.txt", "a+"); if (pFile == NULL) { puts("파일을 열 수가 없습니다!"); return false; } else { puts("파일을 성공적으로 열었습니다!"); } // 파일을 읽어옵니다. while (fgets(Buf, sizeof(Buf), pFile) != NULL) { printf("%s", Buf); } // pFile에 추가할 문구를 Buf에 작성합니다. sprintf_s(Buf, sizeof(Buf), "%s %s(%d): %s\n", __DATE__, __TIME__, __LINE__, __FILE__); printf("추가 문구 : %s\n", Buf); fputs(Buf, pFile); // 문구를 추가합니다. // 파일을 닫습니다. if (fclose(pFile) != 0) { puts("파일을 닫을 수가 없습니다!"); return false; } else { puts("파일을 성공적으로 닫았습니다!"); }
- fopen, fcloase : 열기, 닫기를 수행
- fputs, fgets : 쓰기, 읽기를 수행
모드 | 설명 |
r (read mode) | 읽기 전용 모드 |
w (write mode) | 쓰기 전용 모드 |
a (append mode) | 추가 모드 |
t (text mode) | 해당 파일의 데이터를 텍스트 파일로 인식하고 입출력함. |
b (binary mode) | 해당 파일의 데이터를 바이너리 파일로 인식하고 입출력함. |
x (exclusive mode) | 열고자 하는 파일이 이미 존재하면 파일 개방에 실패함. |
+ (update mode) | 파일을 읽을 수도 있고 쓸 수도 있는 모드 |
모드 예시: wx, rb, wb, wbx, ab, r+, w+, w+x, a+, r+b, rb+, a+b, ab+, w+bx, wb+x
기타
두 변수의 교환
c = b;
b = a;
a = c;
위 코드와 아래 코드는 동일한 기능을 합니다.
a ^= b
b ^= a;
a ^= b;
성능 측정
time.h를 #include하면 clock()함수를 사용할 수 있습니다. clock함수는 CPU클럭을 반환합니다.
start = clock(); //시간 측정 시작 for (int i = 0; i < TIMES; i++) { ptr(A, B); } end = clock(); //시간 측정 끝 result = ((double)end - start);
간의 테스트 결과
성능 체크 중...
Swap : XOR (메모리절약) = 349.000000
Swap (메모리사용) = 291.000000
Swap : XOR [Inline] = 349.000000
Swap [Inline] = 247.000000
XOR swap방법은 퍼포먼스 시간면에서 불리하고, inline함수는 시간면에서 유리한 것을 확인 할 수 있습니다.