C extern 키워드로 모듈 간 변수 접근 제어 완벽 정복

C 언어에서 extern 키워드를 활용해 모듈 간 변수와 함수를 접근 제어하는 개념을 간단히 설명합니다. extern을 통해 전역 변수를 여러 파일에서 효율적으로 공유할 수 있으며, 코드 모듈화를 더욱 견고하게 만들 수 있습니다. 이 기사에서는 extern의 기본 원리와 사용 예시, 디버깅 팁까지 순차적으로 다룰 예정입니다.

목차

extern 키워드 기본 개념


C 언어에서 extern 키워드는 “외부에 선언된 변수나 함수”를 가리킨다는 의미를 갖습니다. 이를 통해 어떤 변수가 다른 소스 파일에 정의되어 있음을 컴파일러와 링커에게 알릴 수 있습니다. 일반적으로 extern 선언은 다음과 같이 나타낼 수 있습니다:

/* 전역 변수 선언(헤더 파일 등) */
extern int globalVar;

/* 전역 변수 정의(소스 파일) */
int globalVar = 10;

위 코드에서 extern int globalVar; 는 “이 변수는 다른 곳에서 정의되었다”는 사실을 명시합니다. 컴파일러는 링커 단계에서 실제로 정의된 globalVar를 연결합니다. 이처럼 extern 키워드는 여러 소스 파일 간에 전역 변수나 함수를 효율적으로 공유하고 모듈화하는 데 핵심적인 역할을 합니다.

선언과 정의의 차이


C 언어에서 ‘선언’은 변수가 또는 함수가 존재한다는 사실만을 컴파일러에게 알려주는 것이며, ‘정의’는 실제 메모리를 할당하거나 함수의 본체를 구현하는 과정을 의미합니다. 예를 들어 다음 코드를 살펴봅시다:

/* 변수 선언 (보통 헤더 파일) */
extern int globalVar;

/* 변수 정의 (실제 소스 파일) */
int globalVar = 10;

여기서 extern int globalVar; 는 선언(Declaration)에 해당하며, int globalVar = 10; 는 정의(Definition)에 해당합니다. 선언은 컴파일러가 해당 변수가 다른 소스 파일에 존재함을 인지하도록 하며, 정의를 통해 실제 변수가 메모리에 할당됩니다. 함수의 경우에도 마찬가지로 함수 원형(prototype)이 선언이고, 본문이 포함된 부분이 정의에 해당합니다. extern 키워드는 이러한 선언과 정의를 명확히 구분하고, 모듈 간 전역 변수나 함수의 사용을 깔끔하게 정리하는 데 도움을 줍니다.

모듈 분할과 extern 사용


프로젝트 규모가 커지면 하나의 소스 파일로 관리하기가 어려워집니다. 이를 해결하기 위해 여러 모듈(소스 파일)로 코드를 분할하고, 각 모듈에서 전역적으로 공유해야 할 변수나 함수를 extern 키워드로 선언합니다. 예를 들어 다음과 같이 코드를 분할할 수 있습니다:

/* utils.h */
#ifndef UTILS_H
#define UTILS_H

extern int globalVar;
void printGlobalVar();

#endif
/* utils.c */
#include <stdio.h>
#include "utils.h"

int globalVar = 100;

void printGlobalVar() {
    printf("globalVar = %d\n", globalVar);
}
/* main.c */
#include "utils.h"

int main() {
    printGlobalVar();
    globalVar = 999;
    printGlobalVar();
    return 0;
}

위 예시에서 utils.h 파일은 globalVarprintGlobalVar() 함수를 extern 선언을 통해 외부로 노출합니다. 실제 정의는 utils.c에서 이루어지고, main.c에서 이를 호출해 원하는 동작을 수행합니다. 이러한 모듈 분할 구조를 통해 코드의 가독성과 유지보수성을 높일 수 있으며, extern 키워드로 변수·함수가 어디에 정의되어 있는지 명확하게 분리할 수 있습니다.

extern 변수 초기화 사례


extern 키워드를 사용할 때 가장 중요한 점은 선언과 정의를 분리하는 것입니다. 선언부에서는 변수를 초기화할 수 없으며, 정의부에서만 초기화가 가능합니다. 예를 들어 다음과 같이 헤더 파일과 소스 파일을 구성할 수 있습니다:

/* counter.h */
#ifndef COUNTER_H
#define COUNTER_H

extern int counter; /* 선언(초기화 금지) */

#endif
/* counter.c */
#include "counter.h"

int counter = 0; /* 정의 및 초기화 */
/* main.c */
#include <stdio.h>
#include "counter.h"

int main() {
    printf("counter = %d\n", counter);
    counter = 100;
    printf("counter = %d\n", counter);
    return 0;
}

위 예시에서 counter.h는 단지 extern int counter;로 선언만 담당하고, 실제 초기화는 counter.c에서 진행합니다. 만약 헤더 파일에서 extern int counter = 0;처럼 동시에 정의를 시도하면 여러 번의 정의 충돌이 발생할 수 있으므로 주의해야 합니다. extern 변수는 한 곳에서만 정의되고, 프로젝트 내 여러 모듈에서 동일한 변수를 공유·사용하도록 도와주는 핵심 역할을 합니다.

함수 접근 제어 방법


C 언어에서 함수도 전역 변수와 마찬가지로 여러 모듈 간에 공유할 수 있습니다. 보통 헤더 파일에 함수 원형을 선언하고, 실제 구현(정의)은 하나의 소스 파일에서 담당합니다. 다음 예시를 통해 살펴봅시다:

/* math_utils.h */
#ifndef MATH_UTILS_H
#define MATH_UTILS_H

extern int add(int a, int b);

#endif
/* math_utils.c */
#include "math_utils.h"

int add(int a, int b) {
    return a + b;
}
/* main.c */
#include <stdio.h>
#include "math_utils.h"

int main() {
    int result = add(2, 3);
    printf("2 + 3 = %d\n", result);
    return 0;
}
  1. 선언부(헤더 파일): extern int add(int a, int b); 같은 함수 원형을 선언하여 다른 모듈에서도 사용할 수 있도록 합니다.
  2. 정의부(소스 파일): 함수의 실제 구현(정의)은 해당 함수가 속한 소스 파일에서만 작성합니다.
  3. 호출부(메인 로직): 다른 모듈(여기서는 main.c)에서 헤더 파일을 포함하고, 선언된 함수를 자유롭게 호출합니다.

이 구조로 분할하면 함수 내부 구현을 숨기면서도(추상화) 필요한 곳에서만 함수를 사용할 수 있습니다. extern 키워드는 함수 선언부 앞에 사용해 “이 함수는 다른 모듈에서 제공된다”는 사실을 명시함으로써 컴파일러와 링커가 올바르게 연결하도록 돕습니다.

응용 예시


다음 예시는 로깅 기능과 네트워크 통신 기능을 분리해 관리하는 실제 프로젝트 상황을 가정해 extern 키워드를 활용하는 방식을 보여줍니다.

/* logger.h */
#ifndef LOGGER_H
#define LOGGER_H

extern void logMessage(const char* msg);

#endif
/* logger.c */
#include <stdio.h>
#include "logger.h"

void logMessage(const char* msg) {
    printf("[LOG]: %s\n", msg);
}
/* network.h */
#ifndef NETWORK_H
#define NETWORK_H

extern int sendPacket(const char* data);

#endif
/* network.c */
#include "network.h"
#include "logger.h"

int sendPacket(const char* data) {
    // 실제 네트워크 송신 코드는 생략
    logMessage("Sending packet...");
    // 데이터를 전송했다고 가정
    return 1; 
}
/* main.c */
#include "logger.h"
#include "network.h"

int main() {
    logMessage("Program started.");
    if (sendPacket("Hello")) {
        logMessage("Packet sent successfully.");
    }
    return 0;
}
  1. logger.h/logger.c: 로그를 출력하는 기능을 logger.c에 정의하고, logger.h에서 extern 키워드를 사용해 외부에 공개합니다.
  2. network.h/network.c: 네트워크 송신 기능을 별도 모듈로 분리합니다. network.c에서 실제 동작을 정의하고, network.h에 extern으로 함수를 선언합니다.
  3. main.c: 프로그램의 진입점이 되는 main 함수에서 logger와 network 함수를 동시에 호출해 필요한 기능을 수행합니다.

이와 같은 구조로 설계하면 로깅과 네트워크 로직이 명확히 분리되어, 유지보수나 변경 작업을 손쉽게 진행할 수 있습니다. extern 키워드를 통해 각 모듈에서 구현한 함수나 변수를 자유롭게 호출할 수 있으며, 코드의 가독성과 확장성을 높일 수 있습니다.

응용 예시

연습 문제


다음 예시 코드를 참조해 extern 키워드의 동작을 직접 확인해 보세요.

/* my_counter.h */
#ifndef MY_COUNTER_H
#define MY_COUNTER_H

extern int myCounter;  // extern으로 선언만

void increaseCounter();
void printCounter();

#endif
/* my_counter.c */
#include <stdio.h>
#include "my_counter.h"

int myCounter = 0;  // 여기서 정의 및 초기화

void increaseCounter() {
    myCounter++;
}

void printCounter() {
    printf("myCounter = %d\n", myCounter);
}
/* main.c */
#include "my_counter.h"

int main() {
    printCounter();    // 초기값(0) 확인
    increaseCounter(); // 1 증가
    printCounter();    // 값 다시 확인
    return 0;
}

① 헤더 파일과 소스 파일을 분리한 이유를 간단히 정리해 보세요.
extern int myCounter; 선언을 헤더 파일이 아닌 my_counter.c에 배치한다면 어떤 문제가 발생할까요?
myCounter를 전역 변수로 대신 static int myCounter로 선언한다면, 다른 모듈에서는 어떻게 접근할 수 있을지 고민해 보세요.

위 문제를 통해 extern 키워드 사용의 장단점을 체감하고, 모듈 분할 시 extern 변수가 어떻게 공유되는지 직접 실습하며 익혀 보세요.

디버깅 시 문제 해결


프로젝트에서 extern 키워드를 사용하는 과정에서 링커 오류나 중복 정의 문제가 발생할 수 있습니다. 다음은 대표적인 오류 상황과 해결책입니다.

1. 다중 정의 오류(Multiple Definition)


같은 전역 변수를 여러 파일에서 동시에 정의했을 때 흔히 발생합니다. 예를 들어, 헤더 파일에서 extern int x = 10;처럼 초기화까지 해버리면, 이 헤더를 포함하는 모든 소스 파일마다 x가 정의되어 링크 단계에서 충돌합니다.

  • 해결책: 헤더 파일에서는 extern int x; 처럼 선언만 하고, 실제 초기화(정의)는 단 한 군데 소스 파일에서만 진행해야 합니다.

2. 선언과 정의 불일치


함수 원형이나 변수 타입이 다른 모듈에서 잘못 선언되어 있을 경우, 링크 단계에서 “unresolved external symbol” 같은 오류가 발생할 수 있습니다.

  • 해결책: 헤더 파일과 실제 정의가 있는 소스 파일 간 함수 및 변수 타입, 매개변수 정보가 일치하도록 수정해야 합니다. 필요하다면 헤더 파일을 최우선으로 수정·관리하여 오타나 타입 누락을 방지합니다.

3. extern 변수 사용 시 초기값 예기치 않음


전역 변수를 extern으로 선언한 뒤, 예상치 못한 값이 대입되는 경우가 있습니다. 이는 초기화가 명확히 어디에서 이뤄지는지 확인하지 않았기 때문일 수 있습니다.

  • 해결책: 변수 정의를 어느 소스 파일에서 어떻게 초기화하는지 명확히 규정하고, 중복 선언이나 다른 모듈에서 재정의를 시도하지 않도록 구조를 정돈해야 합니다.

4. 선언만 있고 정의가 없는 경우


extern int foo;처럼 선언만 해두고 실제 int foo; 정의가 누락되어 있으면 링크 시점에 “undefined reference to foo” 등의 오류가 발생합니다.

  • 해결책: 모든 extern 변수·함수가 반드시 한 곳에서 실제 정의를 갖도록 코드를 작성하세요.

이렇듯 extern 키워드 관련 오류는 대부분 선언과 정의를 혼동하거나, 여러 모듈에서 동시에 정의를 시도해 발생합니다. 헤더 파일을 체계적으로 관리하고, 각 소스 파일에 정의를 단 한 번씩만 배치해 주면 디버깅 시간을 크게 줄일 수 있습니다.

요약


extern 키워드를 올바르게 사용하면, 여러 모듈에서 전역 변수나 함수를 공유하며 C 코드를 더욱 효율적으로 구성할 수 있습니다. 선언과 정의를 분리해 유지보수성을 높이고, 중복 정의나 링크 오류와 같은 문제를 방지할 수 있습니다.

목차