본문 바로가기

awesome-c Beginner 번역/Building C Projects

<비공식 번역>awesome-c Beginner 번역 : 9. Linking

현재위치


1. Configuration

2. Standard directory dectection

3. Source file dependency calculation

4. Header file location

5. Header precompileation

6. Preprocessing

7. Compliation and assembly

8. Object file dependency calculation

9. Linking

10. Installation

11. Resource linking

12. Package generation

13. Dynamic linking

<주의!!>

nethack4.org의 'Building C Projects'의 공식적인 번역이 아니며 수를 받은 것 역시 아닙니다!!




9 : Linking (9 : 링킹)


실행파일을 만들기 위해 결합이 필요한 오브젝트 파일을 알고 있다면, 다음음 실제 실행파일을 만들기 위해서 링커를 사용하는 단계입니다.


링커의 첫번째 임무는 메모리에 모든 것을 배치하는 작업을 수행하는 것입니다; 이는 일반적으로 연속된 블록에 동일한 이름의 섹션을 결합할 것입니다. 현대 시스템에서, 섹션은 스스로의 모든 정체성을 실행파일로 유지하고, 그래서 결합하는 실행파일의 헤더는 간단하고 짧을 것임을 의미합니다. 여기에는 실행파일이 섹션에 대해 알 필요가 있는 다양한 이유가 있습니다. 하나는 실행파일이 동작하는 동안 메모리에 대한 사용권한 구성을 제대로 하는 것입니다; 커널은 메모리의 부분을 읽고, 쓰고 또는 실행하는 컴퓨터의 메모리 관리 유닛(MMU)에게 규칙을 위반하면 충돌의 원인이 됨을 말할 것입니다. Hello World 프로그램의 섹션에서는, .rodata는 읽을 수 있지만 쓰거나 실행할 수는 없습니다. (여러분이 literal 문자열을 쓰거나, 어떠한 영역의 할당을 해제하는 작업에서 오류를 진단하는데 유용한 것); .text는 실행할 수 있고 읽을 수 있지만 쓸 수는 없습니다. (보안관점의 이유 때문인데, 툴체인은 읽기와 실행이 둘다 가능한 메모리를 회피하려 노력합니다.) 또다른 이유는 섹션의 일부는 결과물에서 완전히 최적화 될 수 있기 때문입니다; .bss은 data의 읽기-쓰기를 위해 영역 전체를 0으로 초기화 하는데, 이는 0으로 채워진 다수의 byte로 실행이 필요하지 않기에 일반적인 특별한 경우입니다. 커널은 이러한 규칙을 알고 있지만 실행시간(runtime)에 메모리를 할당하도록 .bss 섹션을 식별할 필요가 있습니다.


링커가 프로그램이 사용해야하는 모든 주소를 결정하면, 기본적으로 각 섹션의 메모리 이미지 파일을 만들고, 컴파일러가 요청한 재배치에 따라 주소를 대체합니다. 적당한 헤더들을 더한 섹션의 목록이 실행 파일로 (즉, 윈도우의 .exe) 실제 디스크에 생성합니다. 그러한 파일의 규칙에 따라, 운영체제(OS)는 기본적으로 모든 섹션의 실행할 수 있는 가상 주소 메모리-맵을 가지며, 프로그램의 실행 시작점을 표시하여 그곳부터 시작한다. (현대 운영체제들은 실행을 위한 메모리 저장을 메모리-맵 파일과 같은 방법으로 최적화할 수 있습니다.)


일부 운영체제에서는 이보다는 간단한 실행파일 포멧을 가지고 있습니다. ADOS의 .com 파일은 단독 섹션의 메모리 이미지이고(0x100의 가상 시작 주소), 섹션의 시작점인 엔트리 포인트, 즉 읽기, 쓰기 그리고 실행입니다. 이러한 포멧의 이로운 점은 특별히 고정시켜야할 모든 정보의 헤더가 필요하지 않다는 것입니다. 반대되는 해로운 점은 8086 메모리 관리는 매우 원시적이기 때문에, 이는 모든 프로그램이 프로그램, 데이터, 스택에서 65280byte의 한계를 가진다는 것을 의미합니다.


비록 링커의 주업무는 메모리 주소 고정과 그에 따른 재배치를 패치하는 것이이지만, 몇가지 다른 작업도 있습니다. 하나는 여러 소스 파일에서 정의된 변수를 다루는 것입니다. 여기 링커가 변수에 대해 잠재적으로 C에서 처리하는 세가지 방법이 있습니다.


1. 정의없는 선언, 예로 extern int foo;

2. 잠정적인 0 초기화로 정의된 변수, 예로 int foo;

3. 명확한 초기화한 정의, 예로 int foo = 0;


첫번째와 세번째 경우는 잘 정의되어 있고 어디에서나 동일한 의미를 갖습니다; 첫번째 경우에서 사용한 extern은 정의되어있지 않음을 명시합니다. 그리고 세번째 경우는 변수를 초기화 했습니다. (따라서 정의해야 합니다.) 두번째 경우는 꽤 모호합니다; 보통의 상태에서 이 문장은 항상 정의되어있습니다. 그러나 모든 C 프로그래머들은 이것이 어떤 메시지를 가진것으로 봅니다. 그리고 때로 이것은 부정확한 선언으로 사용됩니다. 때로 모든 오브젝트 파일이 int foo;를 말하고 그들 중 어느 누구도 초기화해주지 않습니다; 지금 (표준 C에서) 그들 중 하나가 이렇게 선언한다면 다른 선언보다 훨씬 나쁩니다.


따라서 많은 링커는 이런 경우를 다루기 위한 특별한 로직을 갖추고 있으며, 이것이 올바르게 프로그램을 연결하는데 필수적인 요소가 아니라면 대부분의 int foo; 같은 스타일의 선언을 (동일한 메모리 주소에 모든 정의를 할당하여) 변환합니다. [역자주: 문장이 너무 이해하기가 어려워서 한참 고민한 끝에 이 해석이 어울린다고 생각했습니다. 추후 돌아볼 때 다시 판단하겠습니다.]


일부는 아마도 (예를 들면 Mac OS X에서 공유 라이브러리를 빌드) 기술적인 이슈라고 생각하지 않을 수 있습니다. 왜냐면 잘 만들어진 프로그램은 이러한 일을 하지 않아야하기 때문입니다. 그리고 이는 aimake의 인공지능을 혼란스럽게 만들 것이기 때문입니다. aimake는 링커에게 모든 정의(보다 아마도 선언)가 명확해졌음을 전달하라고 컴파일러에게 말합니다. 따라서 이러한 오류를 잡을 수 있습니다. (NetHack 4는 실수로 이 일을 오랫동안 해왔습니다. 이들이 없었던 시기에 두개의 다른  변수 windowproc 를 함께 병합하였습니다. 그런데 공교롭게도 이는 프로그램의 동작에 영향을 미치지 않았습니다. 그래서 aimake가 나에게 이 오류를 이야기하지 않았다면 잡아내지 못했을 것입니다.  하지만 저는 문제를 명확히 다루지 못했고 마침내 제가 NetHack 4의 Mac OS X 포트에서 작업할 때 잘못을 알아 낼 때까지 몇달 동안 그 부분에 매달려 잘못된 부분을 수정했습니다.)


또한 링커는 컴파일러가 할 수 없는 실행하면서 확실한 문제를 잡아낼 수 있습니다; 예를 들어, 어떤 함수가 어디에도 정의되어 있지 않다고 공지할 수 있습니다(정의되지 않은 기호 오류가 만들어내는). 그러나 링커는 여러분이 상상하는 만큼 잡아낼 수 있는건 아닙니다; 만약 여러분이 하나의 파일에 int형 변수를 선언하고 다른 파일에 float을 선언한다면, 링커가 이 둘의 형식 불일치를 잡아낼 확률은 매우 낮습니다 (일부 운영체제에 있는 두개의 심볼의 크기를 알수도 있습니다 하지만 보통 두 자료형은 4byte 길이입니다; 이 두 자료형에 대해 알기위해서 일반적으로 구문분석을 하지 않는 링커가 신뢰할 수 있는 정보는 컴파일러가 생성하지 않는 그것, 디버그 정보 뿐입니다.)



출처1 : https://github.com/aleksandar-todorovic/awesome-c

출처2 : http://nethack4.org/blog/building-c.html