전역 변수, 정적 변수, 내부 연결, 외부 연결 Global Variable, Static Variable, Internal Linkage, External Linkage 본문
전역 변수, 정적 변수, 내부 연결, 외부 연결 Global Variable, Static Variable, Internal Linkage, External Linkage
Hyonii 2022. 1. 14. 22:00전역 변수 Global Variable
함수 외부에서 선언된 변수를 전역 변수(global variable)라고 한다.
전역 변수는 정적 주기(static duration)로, 프로그램이 시작 할 때 생성되고 프로그램이 종료 될 때 파괴된다.
전역 변수는 파일 스코프(or 전역 스코프)를 가진다.
이것은 전역 변수가 정의된 시점 부터 정의된 소스 파일의 끝까지 접근 가능하다는 것을 의미한다.
전역 변수 정의하기 Defining Global Variables
일반적으로 전역 변수는 소스 코드의 맨 위에 있는 #include 밑에 정의하지만, 어디든 상관없다
#include <iostream>
// 함수 외부에서 정의된 변수는 전역 변수(global variable)다.
int g_x; // global variable g_x
const int g_y(2); // global variable g_y
void doSomething()
{
// 전역 변수가 정의된 이후, 프로그램 어디에서든지 접근 가능하다.
std::cout << g_y << "\n";
}
int main()
{
doSomething();
// 전역 변수가 정의된 이후, 프로그램 어디에서든지 접근 가능하다.
g_x = 5;
std::cout << g_y << "\n";
return 0;
}
중첩된 블록(nested block)이 이름이 같은 외부 블록의 변수를 숨기는 것처럼,
전역 변수와 같은 이름을 가진 지역 변수는 전역 변수를 숨긴다.
그러나 전역 범위 연산자(::)를 사용하면 컴파일러는 지역 변수 대신 전역 변수를 사용한다.
#include <iostream>
int value(5); // 전역 변수
int main()
{
int value = 7; // 전역 변수를 숨긴다. (shadowing or hide)
value++; // 지역 변수를 증가시킨다.
::value--; // 전역 변수를 감소시킨다.
std::cout << "global value: " << ::value << "\n";
std::cout << "local value: " << value << "\n";
return 0;
} // 지역 변수 value는 소멸된다.
//This code prints:
//global value: 4
//local value: 8
그러나 전역 변수와 같은 지역 변수를 정의하는 건 피해야 한다.
관습에 따라 일반적으로 전역 변수를 정의할 때는 g_ 접두사를 붙인다.
이 방법은 전역 변수를 식별하는데 편리할 뿐만 아니라 지역 변수와 충돌을 방지하는 데 도움이 된다.
static 과 extern 키워드를 이용한 내부연결 (Internal linkage) / 외부연결(external linkage)
변수는 스코프(scope)와 주기(duration) 외에도 링크(linkage)라는 세 번째 속성이 있다.
링크는 같은 이름의 여러 식별자가 같은 식별자를 참조하는지를 결정한다.
링크가 없는 변수는 정의된 제한된 범위에서만 참조할 수 있다.
지역 변수가 링크가 없는 변수의 예이다.
이름은 같지만 다른 함수에서 정의된 지역 변수는 링크가 없다. 각 변수는 독립적이다.
내부 링크가 있는 변수를 static 변수라고 한다.
static 변수는 변수가 정의된 소스 파일 내에서 어디서나 접근할 수 있지만, 소스 파일 외부에서는 참조할 수 없다.
외부 링크가 있는 변수를 extern 변수라고 한다.
extern 변수는 정의된 소스 파일과 다른 소스 파일 모두에서 접근할 수 있다.
하나의 파일 내에서만 접근할 수 있는 전역 변수를 생성하려면 다음과 같이 static키워드를 사용한다
static int g_x; //g_x is static, and can only be used within this file
int main()
{
return 0;
}
마찬가지로 전역 변수를 외부에서도 접근할 수 있게 만드려면 extern 키워드를 사용하면 된다.
extern double g_y(9.8); //g_y is external, and can be used by other files
//Note : those other files will need to use a 'forward declaration' to access this external variable
int main()
{
return 0;
}
기본적으로 전역 변수는 extern 변수로 간주한다.
그러나 상수(const) 전역 변수는 static 변수로 간주된다.
extern 키워드를 통한 변수 전방 선언 (Variable forward declarations via the extern keyword)
다른 소스 파일에서 선언된 외부 전역 변수를 사용하려면 '변수 전방 선언(variable forward declarations)'을 해야 한다.
extern 키워드는 두 가지 다른 의미가 있다.
어떤 상황에서는 extern 키워드가 '외부 링크가 있는 변수를 의미' 하고
다른 상황에서는 '다른 어딘가에서 정의된 변수에 대한 전방 선언'을 의미한다.
다음은 변수 전방 선언의 예제다
global.cpp
/ 두 개의 전역 변수를 정의한다.
// non-const globals have external linkage by default
int g_x; // external linkage by default
extern int g_y(2); // external linkage by default, so this extern is redundant and ignored
// in this file, g_x and g_y can be used anywhere beyond this point
main.cpp
#include <iostream>
#include "global.cpp"
extern int g_x; // forward declaration for g_x (defined in global.cpp) -- g_x can now be used beyond this point in this file
int main()
{
extern int g_y; // forward declaration for g_y (defined in global.cpp) -- g_y can be used beyond this point in main() only
g_x = 5; std::cout << g_y; // should print 2
return 0;
}
만약 변수 전방 선언이 함수 외부에서 선언되면 소스 파일 전체에 적용된다.
함수 내에서 선언되면 해당 블록 내에서만 적용된다.
변수가 static으로 선언된 경우, 이에 접근하기 위해 변수 전방 선언을 해도 적용되지 않는다.
constant.cpp
static const double g_gravity(9.8);
main.cpp
#include <iostream>
#include "constatnt.cpp"
extern const double g_gravity; //This will satify the compiler that g_gravity exists
int main()
{
std::cout << g_gravity; //This will cause a linker error because the only definition of g_gravity is inaccessible from here
return 0;
}
함수 링크 (function linkage)
함수는 변수와 같은 링크 속성을 가진다.
함수는 항상 외부 링크로 기본 설정되지만 static 키워드를 통해 내부 링크로 설정 할 수 있다.
//This function is declard as static, and can now be used only within this file
//Attempts to access it via a function prototype will fail
static int add(int x,int y)
{
return x + y;
}
함수 전방 선언에는 extern 키워드가 필요하지 않다.
컴파일러는 함수 몸체인지 함수 원형인지 알아서 판단한다.
요약 (Summary)
전역 변수는 전역 스코프(=범위)를 가지며 프로그램의 모든 위치에서 사용할 수 있다.
다른 파일에서 정의된 변수에 접근하려면 키워드 extern을 통해 전방 선언을 해야 한다.
기본적으로 비-상수(not const) 전역 변수는 외부 링크 속성을 가지고 있다.
원하는 경우 static 키워드를 통해 명시적으로 내부 링크 속성을 가지게 할 수 있다.
반대로 상수(const) 전역 변수는 내부 외부 링크 속성을 기본으로 가진다.
원하는 경우 extern 키워드를 통해 외부 링크 속성으로 만들 수 있다.
관습적으로 g_ 접두사를 통해 전역 변수를 식별하기 편하게 한다.
// Uninitialized definition:
int g_x; // defines uninitialized global variable (external linkage)
static int g_x; // defines uninitialized static variable (internal linkage)
const int g_x; // not allowed: const variables must be initialized
// Forward declaration via extern keyword:
extern int g_z; // forward declaration for global variable defined elsewhere
extern const int g_z; // forward declaration for const global variable defined elsewhere
// Initialized definition:
int g_y(1); // defines initialized global variable (external linkage)
static int g_y(1); // defines initialized static variable (internal linkage)
const int g_y(1); // defines initialized static variable (internal linkage)
// Initialized definition w/extern keyword:
extern int g_w(1); // defines initialized global variable (external linkage, extern keyword is redundant in this case)
extern const int g_w(1); // defines initialized const global variable (external linkage)
전역변수가 나쁜이유
좋은 프로그래밍에 대해 충고를 하나 부탁하면, 몇 가지 중에서 가장 자주 듣는 말은 "전역 변수를 피하라!"일 것이다.
전역 변수는 가장 남용 되는 개념 중 하나다.
작은 프로그램에는 해가 없는 것처럼 보이지만 큰 프로그램에서는 종종 문제가 된다.
초보 프로그래머는 대게 함수와 관련되어 있을 때,
매개 변수를 통한 값 전달이 고통스러우므로 전역 변수를 사용하려는 경우가 많다. 그러나 이것은 나쁜 습관이다.
비-상수(non-const) 전역 변수는 완전히 피하는 게 좋다.
전역 변수가 나쁘다고 말하면 모든 전역 변수가 나쁜 것은 아니다.
주로 비-상수(non-const) 전역 변수가 나쁘다.
const가 아닌 전역 변수 사용이 나쁜 큰 이유는 호출되는 함수에 의해 값이 변경될 수 있기 때문이다.
프로그래머는 값이 변경됬다는 것을 알아차리기 쉽지 않을 수 있다.
//전역 변수 선언
int g_mode;
void doSomething()
{
g_mode = 2; //전역변수 g_mode의 값을 2로 설정한다.
}
int main()
{
g_mode = 1; //지역 변수 g_mode가 없기 때문에 전역 변수 g_mode의 값을 1로 설정한다.
doSomething();
//프로그래머는 전역변수 g_mode의 값이 1이라고 생각할 수 있다.
//그러나 doSomething() 함수가 g_mode 값을 2로 바꾸었다.
if(g_mode == 1)
std::cout << "No threat detected.\n";
else
std::cout << "Launching nuclear missiles...\n";
return 0;
}
프로그래머가 g_mode를 1로 설정한 다음 doSomething() 함수를 호출했다.
doSomething() 함수가 g_mode 값을 2로 설정한다는 기능을 알고 있지 않았다면
main() 함수의 나머지 부분은 프로그래머가 기대하는 것처럼 작동하지 않을 것이다.
const가 아닌 전역 변수는 모든 함수를 잠재적으로 위험하게 만든다.
그리고 프로그래머는 어떤 변수가 위험하고 어떤 변수가 위험하지 않은지를 쉽게 알 방법이 없다!
지역 변수는 다른 함수에 직접 영향을 미치지 않으므로 훨씬 안전하다.
또한, 전역 변수는 어디서나 사용할 수 있으므로 변수의 의미를 이해하려면 상당한 양의 코드를 검토해야 한다.
지역 변수를 가능한 한 가까운 곳에 사용하도록 선언하는 이유 중 하나는
변수의 기능을 이해하기 위해 검토해야 할 코드 양을 최소화하기 때문이다.
예를 들어, g_mode 변수가 코드 442번 줄에 정의되었다고 가정하자. g_mode가 제대로 문서화되어 있지 않은 경우, 여러 가지 경우에 사용되는 방법, 유효한 값 및 전체 기능을 이해하기 위해 모든 g_mode 변수의 사용을 검토해야 한다.
전역 변수의 사용을 최소화하고, 전역 변수 대신 지역 변수를 사용해서 함수에 전달하자.
그렇다면 전역변수는 언제 사용하는게 좋을까?
많지 않지만 어떤 경우에는 전역 변수를 사용하면 프로그램의 복잡성을 줄이고, 드물게 다른 변수보다 더 좋을 수 있다.
예를 들어, 프로그램에서 데이터베이스(database)를 사용해서 데이터를 읽고 쓰는 경우,
데이터베이스를 전역 변수로 선언하는 게 좋다.
마찬가지로 프래그램의 오류정보를 덤프할 수 있는 오류 로그가 있는 경우에는 로그 하나만 가지고 있고 어디서나 사용할 수 있으므로 전역으로 정의한다.
사운드 라이브러리 또한 좋은 예다. 사운드 라이브러리를 필요로 하는 모든 함수에 전달하고 싶지 않을 것이다.
소리를 관리하는 사운드 라이브러리가 하나뿐이라면 전역 변수로 선언하고 프로그램이 시작할 때 초기화한 다음 읽기 전용으로 처리하는 게 좋다.
정적 변수 Static Variable
static 키워드는 C++ 언어에서 가장 혼동되는 키워드 중 하나다.
어디에서 사용되는지에 따라 다른 의미를 지니기 때문이다.
전역 변수에서 static 키워드가 사용될 때 '내부 연결 속성'을 적용한다는 것을 알았다.
즉, 변수가 정의된 소스 파일 내에서 사용 가능함을 의미한다.
static 키워드는 블록 내에서 선언된 지역 변수에서도 사용할 수 있다.
지역 변수는 '자동 주기(auto duration)'를 가지며, 정의되는 시점에서 생성되고 초기화되며,
정의된 블록이 끝나는 지점에서 소멸한다.
static 키워드를 사용한 지역 변수는 완전히 다른 의미가 있다.
static 키워드를 사용한 지역 변수는 '자동 주기(auto duration)'에서 '정적 주기(static duration)'로 바뀐다.
이것을 정적 변수(static variable)라고도 부르는데,
생성된 스코프(=범위)가 종료한 이후에도 해당 값을 유지하는 변수다.
또한, 정적 변수는 한 번만 초기화되며 프로그램 수명 내내 지속된다.
#include <iostream>
void incrementAndPrint()
{
int value = 1; //기본적으로 '자동 생명 주기'
++value;
std::cout << value << '\n';
} //value는 여기서 소멸된다.
int main()
{
incrementAndPrint();
incrementAndPrint();
incrementAndPrint();
}
//This prints:
//2
//2
//2
incrementAndPrint() 함수를 호출할 때마다 value 변수가 생성되고 값 1이 할당된다.
그다음 incrementAndPrint() 함수는 값을 2로 증가시킨 value 변수값 2를 출력한다.
incrementAndPrint() 함수의 실행이 완료되면 value 변수는 스코프(=범위)를 벗어나 소멸한다.
이제 위 프로그램의 '정적 생명 주기'를 고려해보자.
위 프로그램과 아래 프로그램의 유일한 차이점은 static 키워드를 사용해서 지역 변수값을 '자동 생명 주기'에서 '정적 생명 주기'로 변경했다는 것이다.
#include <iostream>
void incrementAndPrint()
{
static int s_value = 1; //'static'키워드를 사용한 '정적 생명 주기', 이 줄은 한번만 실행된다.
++s_value;
std::cout << s_value << '\n';
} //s_value는 여기서 소멸되지 않지만, 접근불가
int main()
{
incrementAndPrint();
incrementAndPrint();
incrementAndPrint();
}
//This prints:
//2
//3
//4
위 프로그램에서 s_value는 static으로 선언되었기 때문에 s_value는 한 번만 생성되고 1로 초기화된다.
스코프(=범위)를 벗어나면 소멸하지 않는다.
incrementAndPrint() 함수가 호출될 때마다 s_value 값은 이전에 남겨둔 값이다.
g_를 사용하여 전역 변수에 접두어를 지정하는 것처럼 s_를 사용하여 정적 변수에 접두사를 지정하는 것이 일반적이다.
정적 변수의 가장 일반적인 용도 중 하나는 고유 식별자 생성기다.
프로그램 내에서 다수의 유사한 개체를 처리할 때 각 개체에 고유 ID 번호를 할당하여 식별하는 것이 유용한 경우가 많다. 정적 변수를 사용하면 이 작업을 쉽게 수행할 수 있다.
int generateID()
{
static int s_itemID = 0;
return s_itemID++; //s_itemID의 복사본을 만들고, 실제 s_itemID를 증가시킨 다음 복사본의 값을 반환한다.
}
이 함수가 처음 호출될 때 0을 반환(return)한다.
두 번째로 1을 반환한다. 호출될 때마다 호출 이전보다 1 더 큰 값을 반환한다.
s_itemID는 지역 변수이므로 다른 함수가 "변경"할 수 없다.
정적 변수는 지역 변수와 같은 스코프(=범위)를 가지면서 전역 변수의 이점을 제공한다.
(프로그램이 끝날 때까지 소멸하지 않는다.)
이 특성은 전역 변수를 사용하는 것보다 훨씬 안전하다.
정리해보면
초기화를 안하고 정의만 할 경우
int g_x;
global 이라고 이름 붙여줌
external linkage로 쓸 수도 있음
초기화가 안된 전역변수를 선언
static int g_x;
internal linkage다 .
다른 cpp 파일에서 이 g_x로 접근할 수 없다
초기화가 되지 않은 정적변수다
const int g_x;
이건 안돼. const는 값을 이후에 안바꾸겠다는게 전제조건인데
지금 값을 초기화를 안해주고 있다. 그러면 얘는 의미없는거
forward declaration 일 때는 extern을 붙여준다
extern int g_z;
extern const int g_z;
extern은 const 할 수 있다.
대신 다른 곳 어디선가는 한군데에서만 얘 값을 초기화 해줘야함
int g_y(1); 이 경우도 external linkage
초기화가 된 전역변수이다
static int g_y(1);
외부에서 쓸 수 없는 다른 cpp 파일에서 접근이 불가능한 internal linkage의 정적전역변수를 초기화 까지 하는 경우
const int g_y(1);
이거는 일반적인 상수 선언
이렇게 선언을 하면은 같은 파일 안에서만 접근하는 상수가 된다.
extern int g_w(1);
이 경우 초기화도 하고 전역변수이기도 하고 다른 cpp 파일에서도 접근가능
그런데 이렇게 초기화를 한군데에서 해주면 다른곳에서는 초기화를 하면 안된다
extern const int g_w(1);
이 경우에는 상수고 초기화가 제대로 되었고 외부에서도 접근할 수 있는
(여기서 말하는 외부는 다른 cpp 파일)
전역변수가 기본적으로 internal linkage일 경우
자기가 정의가 된 cpp 파일안에서만 사용될 경우에는 조금 더 범위가 넓은 지역변수처럼 사용하면 되는거.
스코프, 주기 및 링크 요약 (Scope, duration, and linkage summary)
Scope Summary
식별자(identifier)의 스코프(scope)에 따라 접근할 수 있는 위치가 결정된다.
스코프를 벗어난 식별자에 접근할 수 없다.
- 지역 스코프 / 블록 스코프 (local scope / block scope)에 있는 변수는 선언된 블록 내에서만 접근할 수 있다.
- 지역 변수 (local variable)
- 함수 매개 변수 (function parameter)
- 지역 정의 자료형 (Locally defined-type)
- 전역 스코프 / 파일 스코프 (global scope / file scope) 에 있는 변수와 함수는 소스 파일의 모든 위치에서 접근할 수 있다.
- 전역 변수 (global variable)
- 일반 함수 (normal function)
- 전역 정의 자료형 (Locally defined-function)
Duration Summary
변수의 주기(duration)는 변수가 생성되고 소멸하는 시기를 결정한다.
- 자동 주기(automatic duration)인 변수는 정의 지점에서 생성되며, 정의된 블록이 끝나면 소멸한다.
- 일반 지역 변수 (normal local variable)
- 정적 주기(static duration)인 변수는 프로그램이 시작될 때 생성되고, 프로그램이 종료하면 소멸한다.
- 전역 변수 (global variable)
- 정적 변수 (static variable)
- 동적 주기(dynamic duration)인 변수는 프로그래머의 요청에 의해 생성되고, 소멸한다.
- 동적으로 할당된 변수 (Dynamically allocated variables)
Linkage Summary
식별자(identifier)의 링크는 같은 이름의 여러 식별자가 같은 식별자를 참조하는지를 결정한다.
- 링크가 없는 식별자는 식별자가 자신만을 참조한다.
- 일반 지역 변수 (normal local variable)
- 블록 내에서 선언된 사용자 정의 자료형 (Ex. enum, typedef, class)
- 내부 링크가 있는 식별자는 선언 된 파일 내 어디에서나 접근 할 수 있다.
- 정적 전역 변수 (static global variable)
- 상수 전역 변수 (const global variable)
- 정적 함수 (static function)
- 외부 링크가 있는 식별자는 선언된 파일 내 어디에서나 접근할 수 있으며, 다른 파일에서도 접근할 수 있다.
- 일반 함수 (normal function)
- 비-상수 전역 변수 (non-const global variable)
- 외부 전역 변수 (extern global variable)
- 전역 스코프에서 선언된 사용자 정의 자료형 (Ex. enum, typedef, class)
'💘 C++ > 변수범위, 변수형' 카테고리의 다른 글
문자열 std::string (0) | 2022.01.23 |
---|---|
형변환 Type Conversion (0) | 2022.01.17 |
auto 키워드와 자료형 추론 (0) | 2022.01.16 |
Using 문과 모호성 Ambiguity (0) | 2022.01.15 |
지역 변수, 범위, 지속기간 Local variables, scope, duration (0) | 2022.01.11 |