전역 변수, 내부 연결, 외부 연결

전역 변수

전역 변수는 가급적 사용하지 않는 것이 좋지만, 부득이하게 사용해야 하는 경우가 발생하는데 그때 그때 상황이 다르다. 결국 어떻게, 사용해야할지는 프로그래머 본인들이 결정해야 한다.

 

전역 변수의 name hiding

전역 변수와 똑같은 이름으로 선언된 지역 변수가 있으면 name hiding이 수행되어, 지역 변수가 우선적으로 사용된다. 만약 전역 변수의 값에 대해 접근하고 싶으면 `::` global scope operator를 통해 전역 변수에 접근할 수 있ㄷㅇ아.

#include <iostream>

using namespace std;

// name hiding 
 int value = 123;


int main()
{
    cout << value << endl;

    int value = 1;

    cout << ::value << endl; // global scope operator (name hiding 된 후에도 전역 변수를 사용하는 방법)
    cout << value << endl;


    return 0;
}

 

 

Linkage

지역 변수의 경우 linkage가 없다. linking과 linkage의 어감의 뚜렷한 차이를 설명하기는 어렵지만, linkage는 결국 연결 그 자체를 의미하는 뉘앙스가 더 강하다. linking은 각 cpp파일마다 컴파일해서 obj를 만들고 linker가 obj를 "연결"하는 것을 의미한다. linkage 대비 linking은 행위를 묘사하는 것에 조금 더 가까운 것 같다. 지역 변수 같은 경우에는 함수 및 scope 안에서만 존재하기 때문에 링커 입장에서 지역 변수는 밖으로 나올 일이 없어 신경 쓸 필요가 없다.

 

Internal Linkage

변수 선언 후 이 파일안에서는 어디서든 사용할 수 있다. 예를 들어, `static int g_a = 1`로 전역 변수를 선언하면 다른 cpp 파일에서는 접근할 수 없다. 함수 안에 제한된 scope를 갖는 static 변수를 생각하면, 전역으로 선언한 변수도 영역이 제한되어 있다는 느낌으로 생각하면 납득하기가 조금 쉬운 것 같다. (물론 이게 기술적으로 static이 메모리 영역을 제한시킨다는 것으로 의미하는 것은 아님)

#include <iostream>

using namespace std;

static int g_x; // external linkage

int main()
{
	return 0;
}

 

 

External Linkage

한 cpp 파일에서 선언한 변수를 다른 cpp 파일에서 사용 가능한 것을 의미한다. 하지만 이렇게 되는 경우도 웬만하면 피하는게 좋다.

#include <iostream>

using namespace std;

int g_x; // external linkage

int main()
{
	return 0;
}

 

 

 

함수의 외부 연결 예제

아래 코드는 함수를 전방선언한 것으로 body는 다른 곳에 있음을 의마한다. 이를 통해 어떤 cpp 파일에서든 해당 함수를 사용 가능하고, 사실 전방선언된 함수의 앞에는 `extern` 키워드가 생략되어 있다.

 

main.cpp

#include <iostream>

using namespace std;

/* forward declaration
* extern void doSomething()과 동일함. extern이 생략되어 보이는 것
* 어딘가에 다른 파일에 body가 있다는 의미가 되며, 해당 함수는 모든 cpp 파일에서든 사용할 수 있게 된다.
*/
void doSomething(); 

int main()
{
    int a = 10;

    doSomething();

    return 0;
}

 

 

test.cpp

#include <iostream>

// body
void doSomething()
{
    using namespace std;

    cout << "Hello " << endl;
}

 

 

extern 변수

이 코드는 extern을 통해 변수의 몸체가 어딘가에 있다고 선언했지만, 별도의 몸체(메모리)가 없으므로 링킹에러(컴파일에러아님)가 발생한다. 

#include <iostream>

using namespace std;

extern void doSomething(); 
extern int a;

int main()
{
    doSomething();

    cout << a << endl;

    return 0;
}

 

그러므로 이렇게 꼭 초기화가 필요하다,

 

test.cpp

#include <iostream>

extern int a = 123;

// body
void doSomething()
{
    using namespace std;

    cout << "Hello " << endl;
}

 

 

그러면 main.cpp와 test.cpp에서 양쪽에서 초기화를 한다면? 다음과 같은 에러가 발생한다.

 

 

 

헤더 include 주의점

 

MyConstants.h

#pragma once
namespace Constants
{
    const double pi(3.141592);
    const double gravity(9.8);
    // ...
}

 

 

main.cpp

#include <iostream>
#include "MyConstants.h"

using namespace std;

extern void doSomething();

int main()
{
	cout << "In main.cpp file " << Constants::pi << &Constants::pi << endl;

	doSomething();

	return 0;
}

 

 

test.cpp

#include <iostream>

#include "MyConstants.h"

extern int a = 123;

void doSomething()
{
    using namespace std;

    cout << "In test.cpp " << Constants::pi << &Constants::pi << endl;
}

 

 

이렇게 코드가 있고 출력을 보면, Constants::pi의 주소가 서로 다른점을 확인할 수 있다. 즉, 메모리 낭비가 발생한다.

 

 

이러한 상황을 해결 하는 방법은 다음처럼 코드를 정리하는 것이다. 여러 cpp 파일이 하나의 obj 파일의 내용을 링킹해서 가져다 쓸 수 있게 만드는 것이다.

 

MyConstants.h

#pragma once
namespace Constants
{
    extern const double pi;
    extern const double gravity;
    // ...
}

 

 

MyConstants.cpp

// 링킹되기 전, 하나의 obj 파일에 내용이 들어가고 여러 cpp파일이 링킹해서 가져다 쓸 것
namespace Constants
{
    extern const double pi(3.141592);
    extern const double gravity(9.8);
    // ...
}

 

 

이렇게, 메모리를 한 곳에서만 차지하고 같은 주소의 데이터를 반복해서 사용할 수 있게끔 만들 수 있다.

 

최종 정리

#include <iostream>

using namespace std;

/*
	int g_x; // external linkage
    static int g_x; // internal linkage
    const int g_x; // X (꼭 초기화 필요)
    
    // forward declaration
    extern int g_z;
    extern const int g_z; // 가능. 대신 어디선가 한 곳에서만 꼭 초기화 해주고 있어야 함
    
    int g_y(1); // external linkage, 초기화가 된 전역 변수
    static int g_y(1) // internal linkage가 되는 전역 변수를 초기화
    const int g_y(1) // 같은 파일 안에서만 접근
    
    extern int g_w(1); // 초기화, 전역 변수, 다른 cpp 파일에서도 사용가능, 다른 곳에선 초기화하면 안됨
    extern const int g_w(1); // 상수, 초기화, 외부 접근 가능, 
*/


int main()
{
	return 0;
}

결국 cpp 파일별로 생성된 obj 파일들을 링킹할 때 필요한 기준이 external, internal이다.