본문으로 건너뛰기

버그 제보

이상한 점을 알려 주시면 고칠게요. 지금 보고 있는 페이지와 브라우저 정보가 함께 담겨요.

페이지:

Computer Science

C의 스코프와 링키지

작성 2026. 10. 14.추가
목차7개 항목
출처와 참고

팔만코딩경(42Seoul 커뮤니티 위키)의 「[C] Identifier와 Name Space」, 「외부 비노출 함수는 static 함수로 선언하는 것이 '권장'됨」, 「상수(constant)를 직접 정의해 가독성을 높이자」, 「C 코딩 스타일 가이드」 연재를 바탕으로 C 표준 문서와 함께 재구성한 노트다. 저장 클래스 지정자와 변수 선택 기준은 고려사이버대학교 「프로그래밍 기초와 실습(C언어)」(2024-2학기) 강의 내용으로 보강했다.

식별자

식별자(identifier)는 이름이다. C에서 이름은 객체(값을 저장하는 메모리 영역, 즉 변수), 함수, 구조체·공용체·열거형의 태그와 멤버, typedef 이름, 라벨, 매크로를 가리킨다. 같은 이름이 프로그램의 위치에 따라 다른 대상을 가리킬 수 있는데, 그 규칙을 세 가지 축이 정한다.

축질문결정하는 것
스코프(scope)이 이름이 어디서 보이나선언의 위치
이름 공간(name space)같은 위치에서 어떤 종류의 이름인가문법적 맥락 (struct 키워드, . 연산자 등)
링키지(linkage)다른 파일의 같은 이름과 같은 대상인가static, extern과 선언 위치

스코프와 이름 가림

스코프범위
블록{ } 안에서 선언된 이름. 선언 지점부터 블록 끝까지
파일모든 함수 바깥에서 선언된 이름. 선언 지점부터 번역 단위(전처리된 소스 파일) 끝까지
함수라벨(goto의 대상)만 해당. 함수 전체
함수 원형원형의 매개변수 이름. 원형 끝까지

스코프가 겹치면 안쪽 선언이 바깥 선언을 가린다(shadowing). 가려진 대상은 사라지지 않고, 안쪽 블록을 나오면 다시 보인다. 같은 스코프에서 같은 이름을 두 번 정의하는 것은 오류다.

int a = 0;            /* 파일 스코프 */
int main(void)
{
    printf("%d\n", a);   /* 0: 전역 a */
    int a = 42;          /* 여기부터 전역 a를 가림 */
    {
        int a = -42;     /* 블록 안에서 다시 가림 */
        printf("%d\n", a); /* -42 */
    }
    printf("%d\n", a);   /* 42 */
    /* int a = 1; */     /* 같은 블록에서 재정의: 컴파일 오류 */
}

이름 가림은 합법이지만 읽는 사람을 속이기 쉽다. -Wshadow로 경고를 켤 수 있다.

네 가지 이름 공간

C++처럼 namespace를 직접 만들 수는 없지만, C에도 암묵적인 이름 공간이 있다. 같은 스코프라도 이름 공간이 다르면 같은 이름이 충돌하지 않는다.

이름 공간구별하는 방법
라벨goto와 label: 문법
태그 (struct·union·enum 공통)앞에 붙는 키워드
멤버구조체·공용체마다 따로 있다. .이나 -> 앞의 타입으로 구별
일반 식별자변수, 함수, typedef 이름, 열거 상수
struct s { int s; };
struct s s = { .s = 42 };    /* 태그 s, 멤버 s, 변수 s: 모두 다른 공간 → 정상 */
 
typedef struct t { int t; } t;
t t;                          /* typedef 이름 t와 변수 t가 둘 다 일반 식별자 → 오류 */

멤버 이름 공간이 구조체마다 따로 있기 때문에 서로 다른 구조체가 같은 멤버 이름(size, next)을 가질 수 있다. typedef와 변수 이름이 부딪히지 않도록 t_list, list_t처럼 타입 이름에 접두사·접미사를 붙이는 관례가 여기서 나온다.

저장 기간

스코프가 "어디서 보이나"라면 저장 기간은 "언제까지 살아 있나"다.

저장 기간대상수명
자동일반 지역 변수블록에 들어가서 나올 때까지 (보통 스택)
정적전역 변수, static 지역 변수, 문자열 리터럴프로그램 시작부터 끝까지. 초기값이 없으면 0으로 초기화
할당malloc으로 얻은 메모리free할 때까지
스레드thread_local(C23, 이전엔 _Thread_local)스레드가 끝날 때까지

함수 안의 static int count;는 블록 스코프라서 그 함수에서만 보이지만, 값은 호출 사이에 유지된다. 초기화식도 프로그램 시작 전에 한 번만 적용되므로, 함수가 다시 불려 static int count = 1; 줄을 지나도 1로 돌아가지 않는다. 호출 횟수 세기나 한 번만 할 초기화 표시에 쓴다.

변수의 저장 기간과 링키지는 선언 위치와 저장 클래스 지정자(storage-class specifier) 로 정해진다.

지정자뜻비고
auto자동 저장 기간. 블록 안 지역 변수의 기본값이라 쓸 일이 없다C23은 auto x = 1;처럼 타입 추론에도 쓴다(C++11의 auto와 비슷)
register자주 쓰니 레지스터에 두라는 힌트현대 컴파일러는 무시하고 스스로 배치한다. &로 주소를 얻을 수 없다는 제약만 남는다. C++17에서는 제거됐다
static지역 변수면 정적 저장 기간, 파일 스코프면 내부 링키지아래 「링키지와 static」
extern다른 곳(보통 다른 파일)에 정의된 변수를 쓰겠다는 선언. 메모리를 잡지 않는다정의는 한 파일에만 둔다
_Thread_local / thread_local스레드 저장 기간C23부터 thread_local이 키워드

volatile과 const는 저장 클래스가 아니라 타입 한정자(type qualifier) 다. volatile은 하드웨어 레지스터나 시그널 처리기처럼 프로그램 바깥에서 값이 바뀔 수 있는 변수에 붙여, 컴파일러가 읽기·쓰기를 레지스터에 캐시하거나 없애지 못하게 한다. 스레드 간 동기화 수단은 아니다(그건 _Atomic과 뮤텍스의 몫이다).

전역 변수는 어느 함수에서나 바꿀 수 있어 버그 추적이 어렵다. 기본은 자동 변수로 쓰고, 호출 사이에 값을 유지해야 하면 static 지역 변수, 여러 파일이 정말 공유해야 할 때만 extern 전역 변수를 쓴다. 일부 함수만 쓰는 데이터는 전역으로 두지 말고 인수로 넘긴다.

링키지와 static

여러 .c 파일을 각각 컴파일한 뒤 링커가 합칠 때, 파일 스코프 이름이 다른 파일의 같은 이름과 연결되는지를 링키지가 정한다.

링키지선언의미
외부(external)파일 스코프의 함수·변수 기본값, extern모든 번역 단위에서 같은 대상. 헤더에 원형을 두고 공유
내부(internal)파일 스코프에 static그 파일 안에서만 보인다
없음지역 변수, 매개변수링커가 모른다

파일 안에서만 쓰는 함수와 전역 변수는 static으로 선언하는 것이 좋은 습관이다.

  • 다른 파일과 이름이 겹쳐 생기는 링크 오류(duplicate symbol)를 막는다.
  • 읽는 사람이 "이 함수는 이 파일 밖에서 안 쓰인다"는 것을 바로 안다.
  • 컴파일러가 호출하는 곳을 모두 알기 때문에 인라인이나 미사용 함수 제거 같은 최적화를 하기 쉽다.

static이라는 키워드가 지역 변수에서는 저장 기간을, 파일 스코프에서는 링키지를 바꾼다는 점이 헷갈림의 원인이다. 한 프로젝트 안에서는 "외부에 노출하지 않는 함수는 모두 static"처럼 규칙을 정해 일관되게 지키는 것이 중요하다. 접두사(ft_)로 이름 충돌을 피하는 규약도 같은 목적을 달성한다.

기호 상수: #define, enum, const

코드에 박힌 숫자(매직 넘버) 대신 이름 붙은 상수를 쓰면 의미가 드러나고 한곳에서 고칠 수 있다.

방법특징쓰는 곳
#define BUFFER_SIZE 42전처리기의 텍스트 치환. 타입·스코프가 없고 디버거에 이름이 안 남는다조건부 컴파일, -D로 바꿀 값, 헤더 가드
enum { MAX_FD = 1024 };컴파일러가 아는 int 상수. 디버거에 이름이 보이고 배열 크기에 쓸 수 있다서로 관련된 정수 상수 묶음, 상태 값
static const int max = 10;타입이 있는 읽기 전용 변수. C17까지는 상수식이 아니라 배열 크기·case 라벨에 못 쓴다타입이 중요한 값, 실수 상수
constexpr int max = 10; (C23)진짜 컴파일 타임 상수C23을 쓸 수 있을 때 const를 대신

같은 값이라도 의미가 다르면 이름을 따로 짓는다. #define MAX_USERS 153과 #define ERR_READ 153은 값이 같아도 서로 대신 쓰면 안 된다. 이름은 값이 아니라 의도를 담는다.

정리

  • 이름이 무엇을 가리키는지는 스코프(어디서), 이름 공간(무슨 종류), 링키지(다른 파일과 같은가)가 함께 정한다.
  • 안쪽 선언은 바깥을 가린다. 태그·멤버·일반 식별자는 서로 충돌하지 않지만 typedef와 변수는 충돌한다.
  • 파일 밖으로 노출하지 않는 함수와 전역은 static으로 내부 링키지를 준다.
  • 상수는 관련된 정수면 enum, 컴파일 옵션이면 #define, C23이면 constexpr.

관련 노트: GCC 컴파일과 링크, C 사용자 정의 타입과 함수 포인터, C의 역사와 표준

원본 기록6

댓글

GitHub 계정으로 댓글을 남길 수 있어요.