각종 트릭(일단 패스)

류한선·2025년 6월 25일

실기연습-2

목록 보기
41/95

좋아요! 🎯 이번에는 고급 C 트릭 3가지를 준비해 드릴게요.
각각 union 트릭, volatile 트릭, extern 트릭 문제와 해설을 보여드릴게요.


🧠 1) 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은 같은 메모리를 공유하기 때문에 ic 중 하나를 바꿔도 나머지에 영향을 줍니다.

  • 정수 0x41(‘A’), 0x42(‘B’), 0x43(‘C’), 0x44(‘D’)를 리틀 엔디안 시스템에서 메모리에 저장하면:

    • u.c[0] = 0x44 = 'D' (최하위 바이트)
    • u.c[1] = 0x43 = 'C'
    • u.c[2] = 0x42 = 'B'
    • u.c[3] = 0x41 = 'A'
  • 출력:

D A

🧠 2) 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컴파일러 최적화를 방지. 메모리에 반드시 다시 읽도록 지시.

  • 실행 순서:

    • x = 10
    • y = x → y = 10
    • x = 20
    • 출력 → y = 10, x = 20
  • 출력:

10 20

🧠 트릭 포인트:
volatile은 값의 캐싱을 금지. 외부 하드웨어나 인터럽트와 공유될 수 있는 값에서 유용.


🧠 3) extern 트릭 문제

🔍 문제

파일 2개가 있다고 가정해보세요.

file1.c

int count = 100;

file2.c

#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를 참조
  • 초기 count = 100
  • printf("%d\n", count++); → 후치 증가 → 100 출력 후 count=101
  • printf("%d\n", count); → 현재 count=101
  • 출력:
100
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;
}

🧠 숫자 0x41424344 뜯어보기

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


🧠 정리: "왜 이런 일이 일어날까?"

  1. union → 여러 멤버가 같은 메모리 공유

  2. 정수 하나를 넣으면 내부 바이트를 c[] 배열로도 접근 가능

  3. 시스템 엔디안에 따라 메모리 배치가 달라지므로 값이 달라질 수 있음

    • 리틀 엔디안 환경 → D A
    • 빅 엔디안 환경 → A D

좋아요! 🎯 이번에는 C에서 고급 트릭 중 restrict 트릭, inline 트릭, static 트릭을 각각 하나씩 준비해 드릴게요.
문제 형태와 풀이를 하나씩 드리면서, 어디에서 트릭이 나오는지를 강조할 거예요.


🧠 1) 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 키워드 → 이 포인터들이 중첩된 메모리 영역을 공유하지 않는다고 컴파일러에게 약속.
  • 이렇게 되면 컴파일러가 최적화를 과감히 할 수 있음.
  • 위 코드는 정상적으로 1+4, 2+5, 3+6 계산해서 출력함.

✅ 정답

5 7 9

🧠 트릭 포인트:

  • restrict가 없다고 해도 위 출력은 같아요.
  • 하지만 컴파일러 입장에서 restrict를 쓰면 a, b, result가 겹치지 않는다고 믿고 메모리 읽기/쓰기를 최적화할 수 있어 성능 좋아짐.


🧠 2) 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++ → 5 반환 후 a=6
  • 두 번째 a++ → 6 반환 후 a=7
  • 결과 → 5 * 6 = 30
  • 마지막에 a = 7

출력:

30
7


🧠 3) 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 정적 함수 트릭

각 문제마다 코드 + 트릭 포인트 + 풀이까지 철저히 다뤄봅시다.


🧠 1) 매크로 트릭 문제

🔍 문제

다음 코드를 보고 출력값을 예상해 보세요.

#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 = a
  • 수식 = a + (1*a) + 1 = a + a + 1 = 3 + 3 + 1 = 7

✅ 정답

7

🧠 안전한 매크로 쓰기

항상 괄호 쳐주기:

#define SQUARE(x) ((x) * (x))


🧠 2) volatile 트릭 확장 문제

🔍 문제

다음 코드를 보고 출력값을 예상해 보세요.

#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++ 루프는 계속 메모리에서 읽고 쓰도록 동작
  • 하지만 volatile라고 출력값이 변하지 않아요. 단순히 num++ 3번 호출되므로 0→1→2→3

✅ 정답

3

🧠 확장 트릭

이런 트릭은 하드웨어 레지스터나 인터럽트 핸들러 변수에서 유용.
예: 인터럽트 핸들러에서 num이 바뀌는 경우 → volatile 없으면 컴파일러가 캐싱.



🧠 3) extern 정적 함수 트릭 문제

🔍 문제

파일을 두 개로 나눠보자.

file1.c

#include <stdio.h>

static void secret() {
    printf("Secret in file1\n");
}

file2.c

#include <stdio.h>

extern void secret(); // file1의 secret 참조

int main() {
    secret();
    return 0;
}

💡 트릭 포인트

  • file1의 static void secret()내부 연결(translation-unit 내부에서만 유효)
  • file2에서 extern void secret() → 링크 타임에 secret를 찾으려 하지만 없음
  • ❌ 컴파일되더라도 링크 에러 발생

✅ 정답

링커 오류 (undefined reference to 'secret')

🧠 트릭 정리

  • static 함수 → 파일 내부 전용 → 다른 파일에서 extern으로 참조할 수 없음.
  • extern은 정적 함수에 대해 아무 효과 없음!


🎯 트릭 정리

매크로 트릭 → 괄호 없이 매크로 확장되면 수식 꼬임
volatile 트릭 확장 → 컴파일러 캐싱 방지용, 출력값 변동 없음
extern 정적 함수 트릭static은 다른 파일에서 보이지 않음 → 링크 에러


이런 트릭들 연습하면서 시험 대비하시길 추천드려요. 🎉
혹시 더 어려운 트릭 문제다른 주제(스레드 트릭, 메모리 모델 트릭, 구조체 정렬 트릭) 원하시나요?
언제든 말씀해 주세요! 💪

0개의 댓글