C++에서 static 은 처음 접할 때 가장 헷갈리는 키워드 중 하나입니다.
일반 함수에 붙으면 파일 내부에서만 사용되고, 클래스 멤버에 붙으면 객체 없이 전역처럼 사용됩니다.
겉보기에는 서로 정반대처럼 보이기 때문에 혼란이 시작됩니다.
이 글에서는 static 을 하나의 관점으로 정리합니다.
“밖으로 떠돌아다니지 못하게 특정 범위에 고정시키는 것”
여기서 중요한 점은 무엇에 고정되느냐가 상황에 따라 다르다는 것입니다.
static 이 처음 등장한 이유
C/C++ 환경에서는 여러 개의 소스 파일을 컴파일한 뒤 하나의 프로그램으로 연결 (link) 합니다.
이때 각 파일에 정의된 함수 이름은 기본적으로 전역 심볼로 공개됩니다.
// fileA.cpp
void helper() {}
// fileB.cpp
void helper() {}
위와 같은 코드를 컴파일하면, 링커는 동일한 이름의 전역 심볼을 두 개 발견하게 되고, 어느 함수를 사용해야 할지 결정할 수 없어 충돌 에러가 발생합니다. 이 문제를 해결하기 위해 등장한 개념이 static 입니다.
static void helper() {}
함수 이름 앞에 static 을 붙이면 외부에 공개되지 않기 때문에 컴파일이 끝난 뒤 링커는 다른 파일에 동일한 이름의 함수를 찾을 수 없게 됩니다. 즉, 같은 이름의 함수가 파일마다 독립적으로 존재하게 됩니다.
결과적으로 static 은 링크 범위를 파일 내부로 제한 (내부 링크) 하는 역할을 합니다.
header 에 static 함수를 정의
만약 헤더에 static 함수를 정의하고 다른 파일에서 이 함수를 호출하게 된다면, 방금 설명한 것처럼 해당 파일 내부에서 별도의 함수로 존재하게 됩니다.
// helper.h
static void helper() {}
// A.cpp
#include "helper.h"
helper();
// B.cpp
#include "helper.h"
helper();
이 코드를 컴파일러가 보는 실제 모습은 다음과 같습니다.
// helper.h
static void helper() {}
// A.cpp
#include "helper.h"
static helper() {};
// B.cpp
#include "helper.h"
static helper() {};
즉, #include 에 의해 함수가 A.cpp 와 B.cpp 안에 각각 복사가 되면서, 각 파일 내에서 독립된 함수처럼 동작하게 됩니다. 별 문제없이 동작은 하지만, 동일한 코드가 여러 번 생성됩니다.
헤더에 static 선언만 있고 cpp 함수를 정의
// helper.h
static void helper();
// helper.cpp
#include "helper.h"
static void helper() {}
// A.cpp
#include "helper.h"
void Foo()
{
helper();
}
마찬가지로 이 코드를 컴파일러 입장에서 살펴보겠습니다.
// helper.h
static void helper();
// helper.cpp
#include "helper.h"
static void helper() {}
// A.cpp
#include "helper.h"
void Foo()
{
static void helper();
}
static 이 붙으면, 해당 파일에서만 볼 수 있는 함수이기 때문에 helper.cpp 에서는 문제가 발생하지 않습니다.
그런데 A.cpp 는 helper() 를 호출하지만, A.cpp 내부에 helper() 함수의 정의가 없습니다.
그리고 helper() 함수의 정의를 찾으려고 하지만, helper 파일에서 static 으로 외부에 노출되지 않도록 선언했기 때문에 A.cpp 의 helper() 는 함수 정의에 해당되는 내용을 연결시킬 수가 없게 됩니다.
결과적으로 링크 에러가 발생하게 됩니다.
따라서 헤더 파일에 static 함수만 선언하고 정의를 하지 않는것은 전혀 의미가 없습니다.
modern C++ 방식
그런데 static 의 초기 목적인 이름 충돌 방지는 현대 C++ 에 와서는 namespace 또는 class 구조 설계 등으로 초기의 충돌 문제는 대부분 해결되었습니다. 따라서 단순히 충돌 방지를 위해 static 을 사용할 이유는 거의 사라졌습니다.
// 이름없는 namespace
namespace
{
void helper() {}
}
class 에서 static 의 의미
static 이 클래스 멤버 변수나 함수 앞에 붙을 때는 전역처럼 동작합니다.
그런데 지금까지 일반 변수나 함수에서는 지역인 것처럼 이해를 했는데, 클래스로 넘어오면서 전역이라고 하면 혼란스럽게 됩니다. 그런데 클래스에서의 static 은 결과적으로 전역이 된 것일 뿐입니다.
클래스에서의 static 은 클래스 자체에 고정되었다고 이해하면 전혀 혼란스럽지 않습니다.
“밖으로 떠돌아다니지 못하게 특정 범위에 고정시키는 것”
이 말에서 특정 범위의 의미를 다시 한번 떠올려보면,
• 파일에서의 static 은 파일 내부에 고정
• 클래스에서의 static 은 클래스에 고정
여기서 클래스는 객체가 아니라 클래스 그 자체 입니다.
그리고 static 이 클래스에 고정되려면, 클래스 객체의 멤버가 아닌 클래스 그 자체에 고정되어야 하므로, 프로그램 전체에서 하나만 존재하며 전역처럼 사용됩니다.
class Math
{
public:
static int Add(int a, int b)
{
return a + b;
}
};
Math 클래스 자체에 고정된다는 의미는 코드로는 Math::Add() 이고, 이것은 객체 없이 호출이 가능해지므로, 결과적으로 전역으로 선언한 결과가 됩니다.
class Player
{
public :
static int hp;
public :
static int GetHp()
{
return hp;
}
};
여기서 static 멤버 함수는 클래스 소속이기 때문에 this 포인터가 없습니다.
따라서 객체 인스턴스에 접근이 불가합니다. 왜냐하면 실행 시 객체가 존재한다는 보장이 없기 때문입니다.
이러한 이유로 static 함수가 멤버 변수에 접근하기 위해서는 멤버 변수 역시 static 이 될 수밖에 없습니다. ( 물론, 클래스를 인스턴스화하여 객체의 멤버 변수에 접근할 수는 있으나 이것은 static 함수가 일반 멤버 변수에 접근하는 일반적인 방법은 아닙니다. )
그리고 이렇게 class 멤버를 static 으로 선언하는 이유는 객체 수와 관계없이 하나만 존재해야 하며, 모든 객체가 공유될 필요가 있는 경우입니다. 또한 앞에 클래스 이름을 붙이기 때문에 소속이 명확해 지는 장점이 있습니다.
다음은 위험해 보이는 코드입니다.
class Player
{
public :
static int hp;
};
// 링크 오류는 없으나, 오해를 불러일으키는 코드
Player player1;
player1::hp = 10;
Player player2;
player2::hp = 20;
객체가 클래스의 static 멤버 변수나 함수 접근은 가능합니다. 왜냐하면 static 이기 때문에 정적 메모리 공간에 저장되기 때문입니다. 그러나 같은 메모리 공간에 있는 값이나 주소에 접근을 하는 것이기 때문에, 위 코드를 실행하면, player1.hp = 20; 이 됩니다. 따라서 매우 위험한 권장되지 않는 코드입니다.
나아가서, 클래스의 멤버를 직접 static 으로 선언하면 생성과 소멸을 제어할 수 없기 때문에, 이 문제 극복하기 위해 싱글톤 패턴으로 구현할 수 있으나, 이 글에서는 다루지 않겠습니다.
정리
static 은 상황에 따라 역할이 달라 보이지만 본질은 동일합니다.
어디에도 속하지 않고 떠돌던 것을 특정 범위에 고정시키는 기능입니다.
파일에서의 static
외부 접근을 차단하고 파일 내부에 고정시키는 목적으로 사용
클래스에서의 static
객체가 아닌 클래스 자체에 고정되는 의미.
결과적으로 프로젝트에서 전역으로 사용
'C++' 카테고리의 다른 글
| [C++] 순수 가상 함수, 추상 클래스, 그리고 인터페이스 (0) | 2026.02.27 |
|---|---|
| [C++] Enum 의 진화와 실무적인 절충안 (0) | 2026.02.26 |
| [C++] 헤더 파일에서 Namespace와 const를 사용해 설정값 관리하기 (0) | 2026.02.18 |
| [C++] 값 복사는 무조건 느리다는 편견과 현대적 기준 (0) | 2026.02.15 |
| 참조 반환(Return by Reference)의 이해와 활용 (0) | 2026.01.27 |