본문 바로가기

Computer Science/Computer Systems

[Lecture 6] Linking and Loading - Part II

#Software Engineering Aspects

The compilation, Linking and Loading in a typical System

#Local vs Global Symbols

  • 링커의 관점에서, 개별 .o 파일의 기호는 전역적이거나 로컬이다
  • Assembly level: default값은 local이다. 그렇지 않으면 .globl로 state해야함
  • C level: default값은 global이다. 로컬로 만들려면 static으로 state해야함
  • 참고: 로컬/글로벌 변수와 로컬/글로벌 변수의 다르게 사용가능. 여기서 "local"은 컴파일 유닛에 대한 로컬, 즉 .c 파일(헤더 포함)을 의미한다
  • 서로 다른 컴파일 단위의 로컬 기호가 분리되고 서로 충돌하거나 다른 단위의 글로벌 기호와 충돌하지 않는다

#Conflict Resolution Rules for Global Symbols

만약 2개 이상의 모듈이 동일한 이름의 전역 기호를 정의하면 어떻게 될까? 

  • 답: 기호가 "strong"으로 간주되는지 "weak"으로 간주되는지에 따라 다르다
    • strong + strong → 충돌 "잘못 정의됨"
    • strong + weak → weak definition은 무시됨
    • weak + weak → 둘중에 하나의 weak definition이 사용됨
  • 보면서 느꼈을 수도있겠지만 이러한 규칙들은 굉장히 이상하다 (포트란의 공통 블록 탓). 다행스럽게도, 기호를 "weak" 으로 만드는 일반적인 단 한 가지 경우가 있다:
    • 초기화되지 않은 전역 변수 정의 (예: int x; 또는 struct struct type obj)
  • 따라서 서로 다른 컴파일 유닛에서 동일한 전역 변수를 여러 번 정의할 수 있고 링커가 다른 방향으로 회전할 수 있다.

#Understanding Definitions and Declarations in C

#Effect of Definitions and Declarations in a Header File

#Best Practices - Variables

  • 가능한 경우 전역 변수를 사용하지 않는게 좋다. 그러나 변수를 사용해야 하는 경우:
  • 정적 여부에 관계없이 헤더 파일에 전역 변수 정의 하면안됨
    • 대신 정확히 하나의 헤더 파일(외부 파일 포함)에 선언하고 이들을 정의할 하나의 .c 파일을 선택하는게 좋다(이 파일들은 모듈이 소유한다고 하는 것과 동일한 기본 이름을 갖는 경우가 많다)
    • 변수에 대한 초기 값이 제공되었는지 여부에 관계없이 이 작업 수행
    • C++는 One definition rule까지 필요로한다
  • 둘 이상의 .c 파일에 사용되지 않는 한 .c 파일에 정적 전역 변수를 정의하지 않는게 좋다.
    • 여러 파일이 동일한 이름을 정의하면 strong 정의가 충돌하고 weak 정의는 strong/weak 조합과 동일한 복사본을 자동으로 참조한다
    • 대신 정적으로 설정한다 – 캡슐화 극대화

#Best Practices - Functions

  • 둘 이상의 .c 파일에 사용되지 않는 경우 static 파일을 만들고 .c 파일에 보관한다
  • 둘 이상의 .c 파일에 사용되는 경우 헤더 파일에 프로토타입 선언을 배치하는게 좋다. -W missing-protype을 사용하여 이 선언을 적용한다.
    • “implicit declaration” 경고 무시 하지 않기
  • file.c의 함수에 적합한 파일 이름 선택.
  • 컴파일러가 헤더 파일에 인라인할 작은 함수 정의

#Best Practices - Inline Functions

  • 인라인: 컴파일러는 호출 사이트에 함수 본문을 삽입하여 프로시저 호출 오버헤드를 방지하고 최적화를 가능하게 한다
  • 컴파일러가 함수의 소스 코드에 액세스할 수 있어야 함. 따라서 헤더 파일에 대한 정의가 필요하다. 과도한 사용은 컴파일 시간을 증가시킨다.
  • 컴파일러는 선택된 최적화 수준과 휴리스틱을 기반으로 인라인 여부를 결정한다.
  • 그렇다면 어떤 modifier를 사용해야할까?
    • 옵션 1: 정적 또는 정적 인라인. 인라인 추가는 좋은 방법이지만 컴파일러가 실제로 인라인으로 이동하거나 강제하지는 않는다
    • 옵션 2: (C99 이후부터 사용가능) 일반 인라인. 헤더 파일에서 인라인으로, 외부 인라인 선언을 추가할 컴파일 유닛을 정확히 한 개 선택한다.
    • 옵션 2는 컴파일러가 인라인하지 않을 경우 여러 복사본을 피할 수 있지만 더 복잡하며 헤더 전용 라이브러리를 허용하지 않는다는 장점이 있다.

#Conclusion

  • 이 포스트에서는 .c 소스 및 .h 헤더 파일에 선언 및 정의를 배치하기 위한 모범 사례를 알아봄
  • 링커 오류 및 취약한 관행 방지/디버깅
  • 새로운 대안: 전체 프로그램 최적화 기법
    • 링크-시간 최적화(LTO): 컴파일러는 .o 파일에 중간 표현을 저장하며, 최적화 및 코드 생성은 전체 프로그램에서 링크 시간에 수행됨
    • 여러 파일의 소스 코드 연결(소위 "통합 빌드/ unity build"라고 함)