본문 바로가기
임베디드 기초

실무자가 반드시 알아야 할 Static, Extern, Global 변수의 완벽한 차이

by 버그없는토마토 2025. 12. 19.

실무자가 반드시 알아야 할 Static, Extern, Global 변수의 차이

Static, Extern, Global 변수


들어가며

 

AUTOSAR 프로젝트에서 이런 상황이 자주 발생해요:

신입 개발자:
파일1.c:
static int counter = 0;  // ?

파일2.c:
extern int counter;      // ?

main.c:
int global_counter = 0;   // ?

"이게 다 뭐예요? 다 같은 거 아니에요?"

선배:
"음... static은 내부용, extern은 외부용...?"

신입:
"그래서 counter는 어디에 있어요???"

결과: 링킹 오류 + 디버깅 2시간 😭

이 혼동을 끝내자!

이 글에서는 static, extern, global의 정확한 정의, 스코프, 메모리,
그리고 언제 뭘 써야 하는지
명확하게 설명해볼게요


-> 3분 요약

바쁜 당신을 위한 핵심:

- static = "이 파일 안에서만 사용" (내부/private)
- global = "모든 파일에서 사용 가능" (외부/public)
- extern = "다른 파일에 정의된 변수 사용" (참조/링크)

static은 숨김, global은 공개, extern은 빌려쓰기!


Global 변수

1. Global 변수 (전역 변수)란?

정의

Global = 모든 파일에서 접근 가능한 변수

특징:
1. 어디서나 접근 가능 (public)
2. 파일을 넘어 프로그램 전체에서 사용
3. 메모리에 프로그램 시작부터 끝까지 존재
4. 초기화: 없으면 0으로 자동 초기화

Global 변수 예시

// engineControl.c (파일1)
int globalRPM = 0;  // Global 변수
                    // 모든 파일에서 접근 가능!

void EngineControl_Main() {
    globalRPM = 3000;
    printf("RPM = %d\n", globalRPM);
}

// transmissionControl.c (파일2)
extern int globalRPM;  // 외부에서 선언된 globalRPM 사용

void TransmissionControl_Main() {
    if (globalRPM > 5000) {
        // Shift down
    }
}

// main.c
int main() {
    EngineControl_Main();      // globalRPM = 3000
    TransmissionControl_Main(); // globalRPM 읽음
    return 0;
}

결과: globalRPM이 모든 파일에서 공유됨!

Global 변수의 메모리

메모리 레이아웃:

┌─────────────────────┐
│ Code (코드)         │
├─────────────────────┤
│ Data (초기화된 전역) │
│ └─ globalRPM = 0   │ ← Global 변수
├─────────────────────┤
│ BSS (초기화 안 됨)   │
│ └─ globalVar       │
├─────────────────────┤
│ Heap (동적 할당)    │
├─────────────────────┤
│ Stack (로컬 변수)   │
└─────────────────────┘

특징:
- 프로그램 시작 → 메모리 할당
- 프로그램 끝 → 메모리 해제
- 생명주기: 프로그램 전체

Global 변수의 장단점

 장점:
   1️. 모든 함수에서 접근 가능
      └─ 데이터 공유 쉬움

   2️. 함수 매개변수로 전달 불필요
      └─ 함수 시그니처 간단

  3️. 상태 유지 가능
      └─ 함수 호출 사이에 값 보존

 단점:
   1️. 예측 불가능한 수정
      └─ 어디서나 바뀔 수 있음

   2️. 버그 추적 어려움
      └─ globalRPM을 언제 누가 바꿨나?

   3️. 테스트 어려움
      └─ 상태가 누적됨

   4️. 멀티스레드 문제
      └─ Race condition 위험

Static 변수

2. Static 변수 (정적 변수)란?

정의

Static = 이 파일 내부에서만 사용 가능한 변수 (파일 스코프)

특징:
1. 파일 안에서만 접근 가능 (private)
2. 다른 파일에서는 접근 불가
3. 같은 파일 내에서는 global처럼 사용
4. 메모리에 프로그램 시작부터 끝까지 존재
5. 초기화: 없으면 0으로 자동 초기화

Static 변수 (파일 레벨)

// engineControl.c (파일1)
static int engineCounter = 0;  // Static 변수
                               // 이 파일 안에서만!

void EngineControl_Init() {
    engineCounter = 0;
}

void EngineControl_Main() {
    engineCounter++;
    printf("Engine run count: %d\n", engineCounter);
}

// transmissionControl.c (파일2)
// extern int engineCounter;  // X 오류! 접근 불가!

void TransmissionControl_Main() {
    // engineCounter++;  // X 이 변수는 다른 파일 것
                        // 접근 불가!
}

// main.c
int main() {
    EngineControl_Init();
    EngineControl_Main();        // engineCounter = 1
    EngineControl_Main();        // engineCounter = 2
    EngineControl_Main();        // engineCounter = 3
    return 0;
}

결과: engineCounter는 engineControl.c 안에만 존재!
     다른 파일에서는 접근 불가!

Static 변수 (함수 레벨)

// 함수 내의 static 변수
void EngineControl_ProcessData() {
    static int processCount = 0;  // Static 변수 (함수 내)

    processCount++;
    printf("Process count: %d\n", processCount);
}

// main.c
int main() {
    EngineControl_ProcessData();   // processCount = 1
    EngineControl_ProcessData();   // processCount = 2
    EngineControl_ProcessData();   // processCount = 3

    // processCount를 직접 접근?
    // X 불가능! (함수 내 static이므로)

    return 0;
}

특징:
1. 함수 호출마다 값이 유지됨 (로컬과 다름)
2. 함수 내에서만 접근 가능
3. 초기화는 프로그램 시작시 딱 한 번
4. 이후 호출마다 이전 값 유지
변수를 함수 내에서만 쓰는 변수라면 함수 내 static으로 범위를 한정시켜주는 것이죠
근데 이방법은 디버그를 할때 변수 파악이 조금 번거로워서 저는 자주 사용하지는 않습니다

Static 변수의 메모리

메모리 레이아웃:

┌─────────────────────┐
│ Code (코드)         │
├─────────────────────┤
│ Data (초기화된 전역) │
│ ├─ globalRPM       │ ← Global (모든 파일 공유)
│ └─ engineCounter   │ ← Static (engineControl.c만)
├─────────────────────┤
│ Stack (로컬 변수)   │
│ └─ processCount    │ ← Static (ProcessData 함수 내)
└─────────────────────┘

특징:
- Global: 모든 파일에서 접근
- Static (파일): 같은 파일만 접근
- Static (함수): 함수 내만 접근

Static 변수의 용도

1️. 파일 내부 전용 변수
   ├─ 다른 파일에서 수정 방지
   ├─ 실수로 인한 변경 방지
   └─ 캡슐화 (Encapsulation)

   예:
   static int errorCount = 0;  // 에러 카운터 (내부용)

2️. 함수 내 상태 유지
   ├─ 함수 호출 사이 값 보존
   ├─ 호출 횟수 추적
   └─ 이전 값 기억

   예:
   void LogEvent() {
       static int eventID = 0;
       eventID++;  // 매번 증가 (값 유지)
   }

3️. Singleton 패턴
   ├─ 단 하나만 존재하는 객체
   ├─ 스레드 안전성 고려
   └─ 중앙 집중식 관리

   예:
   ConfigManager* GetConfigManager() {
       static ConfigManager manager;  // 한 번만 생성
       return &manager;
   }

Extern 변수

3. Extern 변수 (외부 참조)란?

정의

Extern = 다른 파일에 정의된 변수를 현재 파일에서 사용

특징:
1. "다른 곳에 있으니 거기서 가져와" 선언
2. 메모리 할당 안 함 (참조만)
3. 링킹 시 실제 변수와 연결
4. 링킹 오류 가능 (변수 없으면)

Extern의 작동 원리

파일 구조:

┌─────────────────────────┐
│ engineControl.c (정의)  │
├─────────────────────────┤
│ int globalRPM = 0;      │ ← 메모리에 할당
│                         │
│ void SetRPM(int rpm) {  │
│     globalRPM = rpm;    │
│ }                       │
└─────────────────────────┘
         ↑
         │ (링킹)
         │
┌─────────────────────────┐
│ transmissionControl.c   │
│ (사용)                  │
├─────────────────────────┤
│ extern int globalRPM;   │ ← 메모리 할당 안 함
│                         │  (다른 파일 참조)
│ void CheckRPM() {       │
│     if (globalRPM > ...) │ ← 링킹된 globalRPM 접근
│ }                       │
└─────────────────────────┘

링킹 과정:
1️. 컴파일: 각 파일을 .o로 변환
   └─ engineControl.o: globalRPM 심볼 정의
   └─ transmissionControl.o: globalRPM 심볼 참조

2️. 링킹: .o 파일 연결
   └─ transmissionControl.o에서 globalRPM 찾음
   └─ engineControl.o에서 globalRPM 발견
   └─ 주소 연결 (Relocation)

결과: transmissionControl.c에서 globalRPM 사용 가능!

Extern 변수 예시

// engineControl.c
int globalRPM = 0;  // 정의 (메모리 할당)
int globalTorque;   // 정의 (0으로 초기화)

void SetEngineData(int rpm, int torque) {
    globalRPM = rpm;
    globalTorque = torque;
}

// transmissionControl.c
extern int globalRPM;     // 선언 (참조)
extern int globalTorque;  // 선언 (참조)

void AdjustGear() {
    if (globalRPM > 5000) {
        // 기어 변경
    }
}

// diagnosticServer.c
extern int globalRPM;     // 선언 (참조)
extern int globalTorque;  // 선언 (참조)

void ReportEngineStatus() {
    printf("RPM: %d, Torque: %d\n", 
           globalRPM, globalTorque);
}

// main.c
int main() {
    SetEngineData(3000, 200);
    AdjustGear();           // globalRPM = 3000 읽음
    ReportEngineStatus();   // globalRPM, globalTorque 읽음
    return 0;
}

흐름:
SetEngineData()
    ↓
globalRPM = 3000 (메모리에 저장)
    ↓
AdjustGear()에서 extern으로 참조
    ↓
globalRPM 값 읽음!

Extern 링킹 오류

X 오류 1: 정의 없음

transmissionControl.c:
extern int unknownVar;  // "있을 거야"라고 기대

engineControl.c:
// unknownVar를 정의하지 않음

링킹 결과:
undefined reference to 'unknownVar'

해결:
1️. engineControl.c에 정의 추가
   int unknownVar = 0;

2️. 또는 extern 제거

X 오류 2: 중복 정의

engineControl.c:
int globalCounter = 0;  // 정의

transmissionControl.c:
int globalCounter = 0;  // 또 정의?

링킹 결과:
multiple definition of 'globalCounter'

해결:
transmissionControl.c에서는:
extern int globalCounter;  // 선언만!

X 오류 3: static 변수를 extern으로

engineControl.c:
static int secretCounter = 0;  // Static!

transmissionControl.c:
extern int secretCounter;  // X 접근 불가!

링킹 결과:
undefined reference to 'secretCounter'

이유: secretCounter는 engineControl.c 내부용

해결:
static을 제거하거나 다른 접근 방법 필요

Global, Static, Extern 완벽 비교

4. Global, Static, Extern 완벽 비교

| 특징 | Global | Static (파일) | Static (함수) | Extern |
|------|--------|-------------|------------|--------|
| **선언** | int x; | static int x; | static int x; | extern int x; |
| **정의** |  정의 |  정의 |  정의 |  선언만 |
| **메모리** |  할당 |  할당 |  할당 |  할당 안 함 |
| **스코프** | 모든 파일 | 같은 파일 | 같은 함수 | extern 선언한 파일 |
| **접근** | 어디서나 | 파일 내만 | 함수 내만 | extern 필요 |
| **생명주기** | 전체 | 전체 | 전체 | 전체 |
| **초기화** | 자동 0 | 자동 0 | 자동 0 | 없음 |
| **링킹** | 심볼 노출 | 심볼 숨김 | 심볼 없음 | 심볼 찾음 |

우선순위:
스코프 제한: static > extern > global
단순함: global > extern > static
안전성: static > extern > global

5. 실무 시나리오

시나리오 1: 상태 추적 (Static 사용)

// errorHandler.c
static int errorCount = 0;      // Static (이 파일 내부용)
static int warningCount = 0;

void LogError() {
    errorCount++;
    printf("Error #%d\n", errorCount);
}

void LogWarning() {
    warningCount++;
    printf("Warning #%d\n", warningCount);
}

int GetErrorCount() {
    return errorCount;          // 내부 상태 외부 공개
}

// main.c
int main() {
    LogError();     // errorCount = 1
    LogWarning();   // warningCount = 1
    LogError();     // errorCount = 2

    printf("Total errors: %d\n", GetErrorCount());  // 2

    // errorCount++?  -> 불가능 (static이므로)
    return 0;
}

장점:
1. 실수로 errorCount 직접 수정 불가
2. LogError() 함수를 통해서만 수정
3. 캡슐화 (Encapsulation)
4. 관리 용이

시나리오 2: 데이터 공유 (Global + Extern)

// shared.c (공유 데이터 정의)
int sharedRPM = 0;           // Global 정의
int sharedTorque = 0;

void SetSharedData(int rpm, int torque) {
    sharedRPM = rpm;
    sharedTorque = torque;
}

// engineControl.c
extern int sharedRPM;        // 참조
extern int sharedTorque;

void UpdateEngine() {
    sharedRPM += 100;
}

// transmissionControl.c
extern int sharedRPM;        // 참조
extern int sharedTorque;

void AdjustTransmission() {
    if (sharedRPM > 5000) {
        // Shift
    }
}

// main.c
int main() {
    SetSharedData(3000, 200);
    UpdateEngine();           // sharedRPM = 3100
    AdjustTransmission();     // sharedRPM 읽음
    return 0;
}

장점:
1.모든 파일에서 데이터 접근
2.중앙 집중식 관리 (shared.c)
3.간편한 데이터 공유

단점:
어디서나 수정 가능하기 때문에 자칫 실수가능성이 많음
버그 추적 어려움

시나리오 3: AUTOSAR 구조

// rte.c (RTE 생성 코드)
static Rte_Type_EngineData engineData = {0};  // Static
                                               // RTE 내부 상태

Rte_StatusType Rte_Call_SetRPM(uint16 rpm) {
    engineData.rpm = rpm;
    return E_OK;
}

Rte_StatusType Rte_Call_GetRPM(uint16* rpm) {
    *rpm = engineData.rpm;
    return E_OK;
}

// EngineControl_Impl.c (개발자 구현)
extern Rte_StatusType Rte_Call_SetRPM(uint16 rpm);
extern Rte_StatusType Rte_Call_GetRPM(uint16* rpm);

void EngineControl_Main() {
    Rte_Call_SetRPM(3000);     // RTE 함수 호출
}

// DiagnosticServer_Impl.c
extern Rte_StatusType Rte_Call_GetRPM(uint16* rpm);

void ReadEngineRPM() {
    uint16 currentRPM;
    Rte_Call_GetRPM(&currentRPM);  // RTE 함수 호출
}

구조:
Rte 내부: static으로 데이터 보호
개발자: extern으로 RTE 함수 호출
결과: 안전한 인터페이스!

6. 면접 예상 질문

Q1: "Global과 Static의 차이는?"
A: "Global은 모든 파일에서 접근 가능한 전역 변수이고,
   Static은 그 파일 내부에서만 접근 가능한 변수입니다.
   Static은 '이 파일만 사용' 의도를 나타내는 캡슐화 방법입니다."

Q2: "Extern은 뭐예요?"
A: "다른 파일에 정의된 변수를 현재 파일에서 사용하기 위한 선언입니다.
   메모리 할당은 하지 않고, 링킹 시 실제 변수와 연결됩니다."

Q3: "Static 함수 변수는?"
A: "함수 내에 static 변수를 선언하면,
   함수 호출마다 값이 유지됩니다.
   로컬 변수처럼 함수 내에서만 접근 가능하지만,
   프로그램 시작부터 끝까지 메모리에 존재합니다."

Q4: "Global을 피해야 하는 이유는?"
A: "Global은 어디서나 수정 가능해서 버그를 추적하기 어렵고,
   멀티스레드 환경에서 Race condition 위험이 있습니다.
   Static으로 스코프를 제한하거나,
   함수를 통한 접근으로 제어하는 것이 좋습니다."

Q5: "Extern 링킹 오류 해결은?"
A: "undefined reference 오류가 나면:
   1️. 변수가 정말 정의되어 있나 확인
   2️. 정의 파일도 링킹 대상에 포함되었나 확인
   3. Static이나 스코프 문제 아닌지 확인
   4️. 정의와 extern 선언이 같은 타입인지 확인"

📌 핵심 정리

항목 Global Static (파일) Static (함수) Extern
정의 전역 변수 파일 내 변수 함수 내 변수 외부 참조
스코프 모든 파일 같은 파일 같은 함수 선언한 파일
메모리 할당  O  할당 O  할당 O  할당 안 함
용도 공유 데이터 내부 전용 상태 추적 외부 접근
장점 간편함 캡슐화 상태 유지 명시적
단점 위험함 복잡함 이해 어려움 링킹 오류