좋아요! 🎯 이번에는 고급 C 트릭 3가지를 준비해 드릴게요.
각각 union 트릭, volatile 트릭, extern 트릭 문제와 해설을 보여드릴게요.
union 트릭 문제다음 C 코드를 보고 출력값을 예상해보세요.
#include <stdio.h>
union Data {
int i;
char c[4];
};
int main() {
union Data u;
u.i = 0x41424344; // 메모리에 'A'(0x41) 'B'(0x42) 'C'(0x43) 'D'(0x44) 저장
printf("%c %c\n", u.c[0], u.c[3]);
return 0;
}
union은 같은 메모리를 공유하기 때문에 i나 c 중 하나를 바꿔도 나머지에 영향을 줍니다.
정수 0x41(‘A’), 0x42(‘B’), 0x43(‘C’), 0x44(‘D’)를 리틀 엔디안 시스템에서 메모리에 저장하면:
출력:
D A
volatile 트릭 문제다음 C 코드를 보고 출력값을 예상해보세요.
#include <stdio.h>
int main() {
volatile int x = 10;
int y = x;
x = 20;
printf("%d %d\n", y, x);
return 0;
}
volatile은 컴파일러 최적화를 방지. 메모리에 반드시 다시 읽도록 지시.
실행 순서:
출력:
10 20
🧠 트릭 포인트:
volatile은 값의 캐싱을 금지. 외부 하드웨어나 인터럽트와 공유될 수 있는 값에서 유용.
extern 트릭 문제파일 2개가 있다고 가정해보세요.
int count = 100;
#include <stdio.h>
extern int count; // file1.c의 count 참조
int main() {
printf("%d\n", count++);
printf("%d\n", count);
return 0;
}
extern int count; → file1.c의 전역변수 count를 참조printf("%d\n", count++); → 후치 증가 → 100 출력 후 count=101printf("%d\n", count); → 현재 count=101100
101
🧠 트릭 포인트:
extern은 다른 소스 파일의 전역 변수를 참조할 수 있게 함.
좋아요! 🎯 union 트릭 문제를 아주 자세히 디버깅해 드릴게요.
이번엔 메모리 구조를 그림처럼 이해하기 쉽게 설명하겠습니다.
#include <stdio.h>
union Data {
int i;
char c[4];
};
int main() {
union Data u;
u.i = 0x41424344;
printf("%c %c\n", u.c[0], u.c[3]);
return 0;
}
16진수 0x41 42 43 44
→ 1바이트씩 끊으면:
| 바이트 위치 | 값(16진수) | ASCII |
|---|---|---|
| 최하위 바이트 | 0x44 | 'D' |
| 두 번째 바이트 | 0x43 | 'C' |
| 세 번째 바이트 | 0x42 | 'B' |
| 최상위 바이트 | 0x41 | 'A' |
💡 대부분의 PC(x86, x64 등)는 리틀 엔디안(little-endian) 방식을 사용합니다.
리틀 엔디안:
→ 낮은 메모리 주소에 최하위 바이트부터 차곡차곡 저장.
주소 낮음 -> 높음
+0: 0x44 ('D')
+1: 0x43 ('C')
+2: 0x42 ('B')
+3: 0x41 ('A')
union Data 안의 구조union의 멤버들은 같은 메모리 주소를 공유하므로:
u.i = 0x41424344; 대입 → 위의 바이트들 메모리에 박힘.u.c[0] → 메모리의 0번째 바이트 → 0x44 → 'D'u.c[3] → 메모리의 3번째 바이트 → 0x41 → 'A'따라서 printf("%c %c\n", u.c[0], u.c[3]); → D A
union → 여러 멤버가 같은 메모리 공유
정수 하나를 넣으면 내부 바이트를 c[] 배열로도 접근 가능
시스템 엔디안에 따라 메모리 배치가 달라지므로 값이 달라질 수 있음
D AA D좋아요! 🎯 이번에는 C에서 고급 트릭 중 restrict 트릭, inline 트릭, static 트릭을 각각 하나씩 준비해 드릴게요.
문제 형태와 풀이를 하나씩 드리면서, 어디에서 트릭이 나오는지를 강조할 거예요.
restrict 트릭 문제다음 코드를 보고 출력값을 예상해보세요.
#include <stdio.h>
void add_arrays(int *restrict a, int *restrict b, int *restrict result, int n) {
for (int i = 0; i < n; i++) {
result[i] = a[i] + b[i];
}
}
int main() {
int arr1[3] = {1,2,3};
int arr2[3] = {4,5,6};
int arr3[3];
add_arrays(arr1, arr2, arr3, 3);
printf("%d %d %d\n", arr3[0], arr3[1], arr3[2]);
return 0;
}
restrict 키워드 → 이 포인터들이 중첩된 메모리 영역을 공유하지 않는다고 컴파일러에게 약속.5 7 9
🧠 트릭 포인트:
restrict가 없다고 해도 위 출력은 같아요.- 하지만 컴파일러 입장에서
restrict를 쓰면a,b,result가 겹치지 않는다고 믿고 메모리 읽기/쓰기를 최적화할 수 있어 성능 좋아짐.
inline 트릭 문제다음 코드를 보고 출력값을 예상해보세요.
#include <stdio.h>
inline int square(int x) {
return x * x;
}
int main() {
int a = 5;
printf("%d\n", square(a++));
printf("%d\n", a);
return 0;
}
여기서 square(a++)를 호출합니다.
inline 함수라 하더라도 호출한 코드를 복사/붙여넣기 하듯 확장하기 때문에 이렇게 동작:square(a++) → 확장됨 → (a++) * (a++) 처럼 동작할 수 있음.이건 매크로처럼 대체될 수 있다는 트릭이므로 컴파일러마다 달라질 수 있음.
→ inline 함수라고 반드시 그런 건 아니지만, 매크로 트릭 문제를 연상하게 함.
→ 하지만 C 표준에서는 inline은 단순 힌트일 뿐 반드시 확장되지 않을 수 있으므로 컴파일러마다 다릅니다.
인수를 미리 지역변수에 담은 후 호출하는 것이 좋아요.
square(a++) 확장됨 → (a++) * (a++)a++ → 5 반환 후 a=6a++ → 6 반환 후 a=75 * 6 = 30출력:
30
7
static 트릭 문제다음 코드를 보고 출력값을 예상해보세요.
#include <stdio.h>
void counter() {
static int count = 0;
count++;
printf("%d\n", count);
}
int main() {
counter();
counter();
counter();
return 0;
}
static 지역변수 → 함수가 호출될 때마다 초기화되지 않고 값 유지count++1
2
3
✅ restrict 트릭
→ 컴파일러에게 "포인터끼리 메모리 안 겹친다"고 알려서 성능 최적화 유도
✅ inline 트릭
→ 인라인 함수 호출을 코드 복사해 확장할 수 있음.
→ 단, 부작용 있는 인자(a++)를 넣으면 연산 중첩 위험!
✅ static 트릭
→ 함수 안에서 static → 호출 사이 값 유지, 초기화 1번만 일어남
좋아요! 🎯 이번엔 더 고급 트릭 문제들을 준비해 드릴게요.
이번 주제:
✅ #define 매크로 트릭
✅ volatile 트릭 확장
✅ extern 정적 함수 트릭
각 문제마다 코드 + 트릭 포인트 + 풀이까지 철저히 다뤄봅시다.
다음 코드를 보고 출력값을 예상해 보세요.
#include <stdio.h>
#define SQUARE(x) x * x
int main() {
int a = 3;
int result = SQUARE(a + 1);
printf("%d\n", result);
return 0;
}
SQUARE(a+1) → a+1 * a+1로 확장.int result = a + 1 * a + 1;
* 우선순위 높음 → 1*a = aa + (1*a) + 1 = a + a + 1 = 3 + 3 + 1 = 77
항상 괄호 쳐주기:
#define SQUARE(x) ((x) * (x))
다음 코드를 보고 출력값을 예상해 보세요.
#include <stdio.h>
int main() {
volatile int num = 0;
for (int i = 0; i < 3; i++) {
num++;
}
printf("%d\n", num);
return 0;
}
volatile → 컴파일러 최적화하지 말고 메모리에서 항상 읽고 쓰기num++ 3번 호출되므로 0→1→2→33
이런 트릭은 하드웨어 레지스터나 인터럽트 핸들러 변수에서 유용.
예: 인터럽트 핸들러에서 num이 바뀌는 경우 → volatile 없으면 컴파일러가 캐싱.
파일을 두 개로 나눠보자.
#include <stdio.h>
static void secret() {
printf("Secret in file1\n");
}
#include <stdio.h>
extern void secret(); // file1의 secret 참조
int main() {
secret();
return 0;
}
static void secret() → 내부 연결(translation-unit 내부에서만 유효)extern void secret() → 링크 타임에 secret를 찾으려 하지만 없음링커 오류 (undefined reference to 'secret')
static 함수 → 파일 내부 전용 → 다른 파일에서 extern으로 참조할 수 없음.extern은 정적 함수에 대해 아무 효과 없음!✅ 매크로 트릭 → 괄호 없이 매크로 확장되면 수식 꼬임
✅ volatile 트릭 확장 → 컴파일러 캐싱 방지용, 출력값 변동 없음
✅ extern 정적 함수 트릭 → static은 다른 파일에서 보이지 않음 → 링크 에러
이런 트릭들 연습하면서 시험 대비하시길 추천드려요. 🎉
혹시 더 어려운 트릭 문제나 다른 주제(스레드 트릭, 메모리 모델 트릭, 구조체 정렬 트릭) 원하시나요?
언제든 말씀해 주세요! 💪