처리중입니다. 잠시만 기다려주세요.
TTJ 코딩클래스
정규반 단과 자료실 테크 뉴스 코딩 퀴즈
테크 뉴스
Hacker News 2026.04.30 59

C 함수에 레지스터 인자를 적게 넘기면 생기는 무서운 일

Hacker News 원문 보기
C 함수에 레지스터 인자를 적게 넘기면 생기는 무서운 일

호출 규약, 보이지 않지만 모든 걸 좌우하는 약속

Microsoft의 레이먼드 첸이 운영하는 'The Old New Thing' 블로그에서 또 흥미로운 저수준 이야기를 다뤘어요. 이번 주제는 호출 규약(calling convention)이에요. 처음 듣는 분도 있을 텐데요, 이게 뭐냐면 "함수를 호출할 때 인자를 어디에 어떻게 놓을지에 대한 약속"이에요.

예를 들어 add(3, 5)라는 함수를 부른다고 해봅시다. 컴파일러는 3과 5를 어딘가에 놓고 함수에게 넘겨야 해요. 보통 빠른 통신을 위해 CPU 레지스터(CPU 안에 있는 가장 빠른 저장소)에 놓아요. x86-64에서 윈도우는 첫 4개 인자를 RCX, RDX, R8, R9 레지스터에 넣고, 리눅스는 RDI, RSI, RDX, RCX, R8, R9에 넣는 식이에요. 다섯 번째부터는 메모리(스택)에 쌓고요.

만약 약속을 어기면?

이 글의 핵심은 "만약 함수가 6개 인자를 받기로 되어 있는데, 호출하는 쪽에서 4개만 넣어서 부르면 무슨 일이 일어나나?"예요. 답: 컴파일러는 모르고, CPU도 모르고, 그냥 굴러가요. 그게 무서운 점이에요.

C 언어는 함수 시그니처를 헤더 파일로만 알리는 구조라서, 헤더 선언이 잘못되어 있거나 캐스팅으로 우회하면 컴파일러가 "인자가 모자라네!"라고 잡아주지 못할 수 있어요. 실행 시점에 함수가 시작되면, 함수 본체는 "내가 약속받은 자리"에 있는 값을 그냥 가져다 써요. 호출자가 거기에 아무것도 안 넣어놨다면? 이전에 누가 거기에 남겨둔 쓰레기 값을 그대로 인자라고 믿고 동작해요.

진짜 무서운 시나리오

레이먼드는 이게 단순히 "잘못된 값이 들어가서 결과가 이상하게 나온다"로 끝나지 않는다는 점을 강조해요. 예를 들어 함수가 받지 못한 인자가 사실은 "이 메모리 영역의 크기"였다면? 함수는 쓰레기 값을 진짜 크기로 알고 그 만큼 메모리를 읽거나 써요. 버퍼 오버플로가 그냥 발생하는 거예요. 또는 그게 함수 포인터였다면? 쓰레기 주소로 점프해서 임의의 코드를 실행할 수도 있고요.

더 까다로운 건, 이게 "가끔만" 터진다는 점이에요. 운 좋게 그 레지스터에 0이 들어가 있으면 코드는 멀쩡히 동작하는 것처럼 보여요. 다른 함수 호출 흐름을 거쳐 들어오면 거기에 우연히 정상 범위 값이 들어 있어 또 멀쩡해 보이고요. 그러다가 어느 날 컴파일러를 업그레이드하거나, 코드 한 줄 바꾸거나, 최적화 옵션을 바꾸면 갑자기 크래시가 나요. "아무것도 안 바꿨는데 왜 깨지지?"의 전형적인 원인 중 하나죠.

왜 컴파일러는 못 막아주나

컴파일러는 자기가 보는 모든 코드에 대해선 인자 개수를 검증해요. 하지만 다음과 같은 경우엔 한계가 있어요.

첫째, 다른 라이브러리를 동적 링크할 때예요. 헤더에 선언된 시그니처와 실제 라이브러리 함수의 시그니처가 다르면, 컴파일 타임엔 알 길이 없어요. 라이브러리 버전이 바뀌면서 함수 시그니처가 변경됐는데 헤더만 안 업데이트한 경우가 대표적이죠.

둘째, 함수 포인터 캐스팅을 한 경우예요. C에서는 (void*) 같은 캐스팅으로 컴파일러의 타입 검사를 우회할 수 있거든요. 이러면 컴파일러는 사실상 "네가 책임져" 모드가 돼요.

셋째, FFI(외부 언어 연동) 상황이에요. Python에서 C 라이브러리를 부르거나, JavaScript에서 WebAssembly를 부를 때 호출 규약이 한쪽만 잘못 정의되어 있으면 똑같은 문제가 터져요.

한국 개발자에게는

네이티브 코드를 자주 다루지 않는 백엔드/프론트엔드 개발자라도 이걸 알아두면 좋은 이유가 있어요. Node.js의 N-API 모듈, Python의 ctypes, Rust의 FFI, JNI 등 "고수준 언어에서 C 함수를 부르는 다리"를 만들 때 이 함정이 그대로 적용되거든요. 시그니처를 한 글자라도 잘못 적으면 운 좋으면 크래시, 운 나쁘면 메모리 오염으로 이어져요.

실무 팁으로는, 헤더와 구현이 한 저장소에서 같이 관리되도록 하고, 가능하면 자동 바인딩 생성기(rust-bindgen, cffi 등)를 쓰세요. 사람이 손으로 시그니처를 옮겨 적는 순간이 위험 포인트예요. 또 정적 분석 도구(Clang Static Analyzer, MSVC 분석 옵션)를 켜두면 일부 케이스는 잡아줘요.

정리

C의 호출 규약은 작은 약속이지만, 어기면 메모리 안전성과 보안이 한순간에 무너져요. 고수준 언어가 안전해진 이유 중 하나가 바로 이 약속을 사람이 직접 다룰 일을 없앤 거예요. 여러분은 FFI나 네이티브 모듈 관련해서 디버깅하다가 머리 싸맨 경험이 있나요?


🔗 출처: Hacker News

이 뉴스가 유용했나요?

이 기술을 직접 배워보세요

파이썬으로 자동화를 시작해보세요

파이썬 기초부터 자동화까지 실전 강의.

파이썬 강의 보기

"비전공 직장인인데 반년 만에 수익 파이프라인을 여러 개 만들었습니다"

실제 수강생 후기
  • 비전공자도 6개월이면 첫 수익
  • 20년 경력 개발자 직강
  • 자동화 프로그램 + 소스코드 제공

매일 AI·개발 뉴스를 받아보세요

주요 테크 뉴스를 매일 아침 이메일로 전해드립니다.

스팸 없이, 언제든 구독 취소 가능합니다.