수색…


소개

C 언어는 전통적으로 컴파일 된 언어입니다 (해석 된 것과 반대). C 표준은 변환 단계를 정의하며이를 적용한 제품은 프로그램 이미지 (또는 컴파일 된 프로그램)입니다. 에서 단계는 §5.1.1.2에 나열되어 있습니다.

비고

파일 이름 확장자 기술
.c 소스 파일. 보통 정의와 코드를 포함합니다.
.h 헤더 파일. 보통 선언을 포함합니다.
.o 개체 파일. 기계어로 컴파일 된 코드.
.obj 오브젝트 파일의 대체 확장.
.a 라이브러리 파일. 오브젝트 파일 패키지.
.dll 동적 링크 라이브러리 (Windows).
.so 많은 유닉스 계열 시스템에서 공유 객체 (라이브러리).
.dylib OSX의 동적 링크 라이브러리 (Unix 계열).
.exe , .com Windows 실행 파일. 오브젝트 파일과 라이브러리 파일을 링크하여 형성됩니다. 유닉스 계열 시스템에서는 실행 파일을위한 특별한 파일 이름 확장자가 없다.
POSIX c99 컴파일러 플래그 기술
-o filename 출력 파일 이름 예. ( bin/program.exe , program )
-I directory direrctory 에서 헤더를 검색 direrctory .
-D name 매크로 name 정의
-L directory directory 에서 라이브러리를 검색하십시오.
-l name 링크 라이브러리 libname .

POSIX 플랫폼의 컴파일러 (Linux, 메인 프레임, Mac)는 c99 호출되지 않아도 일반적으로 이러한 옵션을 허용합니다.

GCC (GNU 컴파일러 컬렉션) 플래그 기술
-Wall 일반적으로 받아 들여지는 모든 경고 메시지를 유용하게 사용할 수 있습니다.
-Wextra 더 많은 경고 메시지를 허용하고 너무 시끄 럽습니다.
-pedantic 코드가 선택된 표준을 위반하는 경우 강제로 경고합니다.
-Wconversion 암시 적 변환시 경고를 활성화하고주의해서 사용하십시오.
-c 연결하지 않고 소스 파일을 컴파일합니다.
-v 컴파일 정보를 인쇄합니다.
  • gcc 는 POSIX 플래그와 많은 다른 것들을 받아 들인다.
  • POSIX 플랫폼 ( clang , 벤더 특정 컴파일러)의 많은 다른 컴파일러도 위에 나열된 플래그를 사용합니다.
  • 더 많은 옵션을 보려면 GCC 호출을 참조하십시오.
TCC (Tiny C 컴파일러) 플래그 기술
-Wimplicit-function-declaration 암시 적 함수 선언에 대해 경고합니다.
-Wunsupported TCC에 의해 무시되는 지원되지 않는 GCC 기능에 대해 경고합니다.
-Wwrite-strings 문자열 상수를 char * 대신 const char * 형식으로 만듭니다.
-Werror 경고가 발행되면 컴파일을 중단하십시오.
-Wall -Werror , -Wunusupported-Wwrite strings 제외한 모든 경고를 활성화하십시오.

링커

링커의 작업은 많은 수의 개체 파일 ( .o 파일)을 바이너리 실행 파일에 연결하는 것입니다. 링크 과정은 주로 기호 주소를 숫자 주소로 해석하는 것과 관련이 있습니다. 링크 프로세스의 결과는 일반적으로 실행 가능 프로그램입니다.

(오브젝트 파일을 링크 과정, 링커, 명령 줄에 지정된 모든 오브젝트 모듈을 데리러 앞에 일부 시스템 고유의 시작 코드를 추가하고 다른 오브젝트 파일의 외부 정의와 개체 모듈에있는 모든 외부 참조를 해결하려고 명령 줄에서 직접 지정하거나 라이브러리를 통해 암시 적으로 추가 할 수 있습니다. 그런 다음 객체 파일에 로드 주소 를 할당합니다. 즉, 코드 및 데이터가 완성 된 프로그램의 주소 공간에서 끝나는 위치를 지정합니다. 일단로드 주소가 있으면 오브젝트 코드의 모든 기호 주소를 대상 주소 공간의 "실제"숫자 주소로 바꿀 수 있습니다. 이제 프로그램을 실행할 준비가되었습니다.

여기에는 컴파일러가 소스 코드 파일에서 생성 한 개체 파일과 미리 컴파일되어 라이브러리 파일로 수집 된 개체 파일이 포함됩니다. 이러한 파일의 이름은 .a 또는 .so 끝나며 일반적으로 링커가 대부분의 위치를 ​​알고 필요에 따라 자동으로 링크하기 때문에 대개 알 필요가 없습니다.

링커의 암시 적 호출

전 처리기와 마찬가지로 링커는 종종 ld 라고하는 별도의 프로그램입니다 (예 : Linux는 collect2 사용합니다). 또한 전 처리기와 마찬가지로 링커는 컴파일러를 사용할 때 자동으로 호출됩니다. 따라서 링커를 사용하는 일반적인 방법은 다음과 같습니다.

% gcc foo.o bar.o baz.o -o myprog

이 줄은 컴파일러에게 3 개의 오브젝트 파일 ( foo.o , bar.obaz.o )을 myprog 라는 이진 실행 파일로 링크하도록 지시합니다. 이제 실행할 수있는 myprog 라는 파일이 있습니다.이 파일은 시원하고 유용하게 사용할 수 있습니다.

링커의 명시 적 호출

링커를 직접 호출 할 수도 있지만, 이는 거의 권장 할만한 사항이 아니며 대개 플랫폼별로 다릅니다. 즉, Linux에서 작동하는 옵션은 Solaris, AIX, macOS, Windows 및 기타 다른 플랫폼에서 반드시 작동하지는 않습니다. GCC로 작업한다면 gcc -v 를 사용하여 무엇을 대신해서 실행되는지 볼 수 있습니다.

링커 옵션

또한 링커는 동작을 수정하기 위해 몇 가지 인수를 취합니다. 다음 명령은 gcc에게 foo.obar.o 를 연결하도록 지시하지만 ncurses 라이브러리도 포함합니다.

% gcc foo.o bar.o -o foo -lncurses

실제로 이것은 다소 차이가 있습니다.

% gcc foo.o bar.o /usr/lib/libncurses.so -o foo

( libncurses.solibncurses.a 일 수 있지만 ar 만든 아카이브 일뿐입니다). 오브젝트 파일 다음에 라이브러리를 나열해야합니다 (경로 이름 또는 -lname 옵션을 사용하여). 정적 라이브러리에서는 지정된 순서가 중요합니다. 종종 공유 라이브러리에서 순서는 중요하지 않습니다.

많은 시스템에서 수학 함수 ( <math.h> )를 사용하는 경우 수학 라이브러리를로드하려면 -lm 을 지정해야하지만 Mac OS X 및 macOS Sierra에서는이를 요구하지 않습니다. 리눅스와 다른 유닉스 시스템에는 별도의 라이브러리가 있지만 macOS에는없는 라이브러리가있다. POSIX 스레드와 POSIX 실시간 라이브러리, 네트워킹 라이브러리가 그 예이다. 따라서 연결 프로세스는 플랫폼에 따라 다릅니다.

기타 컴파일 옵션

이것은 자신의 C 프로그램을 컴파일하기 시작할 때 알아야 할 모든 것입니다. 일반적으로 -Wall 명령 줄 옵션을 사용하는 것이 좋습니다.

% gcc -Wall -c foo.cc

-Wall 옵션을 사용하면 컴파일러가 합법적이지만 모호한 코드 구조에 대해 경고하게되므로 많은 버그를 조기에 발견하는 데 도움이됩니다.

컴파일러에서 (선언되었지만 사용되지 않은 변수, 값을 반환하는 것을 잊어 버린 변수를 포함하여) 더 많은 경고를 던지기를 원한다면, 이름에도 불구하고 -Wall 이 돌아 가지 않기 때문에이 옵션 세트를 사용할 수 있습니다 가능한 모든 경고 사항 :

% gcc -Wall -Wextra -Wfloat-equal -Wundef -Wcast-align -Wwrite-strings -Wlogical-op \
>     -Wmissing-declarations -Wredundant-decls -Wshadow …

참고 clang 옵션이 -Weverything 정말 모든 경고를 켜 않습니다 clang .

파일 형식

C 프로그램을 컴파일하려면 5 가지 종류의 파일로 작업해야합니다.

  1. 소스 파일 :이 파일은 함수 정의를 포함하고 있으며 규약에 따라 .c.c 이름 .c 집니다. 참고 : .cc.cpp 는 C ++ 파일입니다. C 파일이 아닙니다 .
    예 : foo.c

  2. 헤더 파일 :이 파일에는 함수 프로토 타입과 다양한 프리 프로세서 설명이 들어 있습니다 (아래 참조). 소스 코드 파일이 외부에서 정의 된 함수에 액세스 할 수있게합니다. 헤더 파일은 규약에 따라 .h 로 끝납니다.
    예 : foo.h

  3. 오브젝트 파일 :이 파일은 컴파일러의 출력으로 생성됩니다. 이진 형식의 함수 정의로 구성 되나 스스로 실행할 수는 없습니다. 일부 운영 체제 (예 : Windows, MS-DOS)에서는 .obj 끝나는 경우가 있지만 일반적으로 객체 파일은 .o 로 끝납니다.
    예 : foo.o foo.obj

  4. 이진 실행 파일 : "링커"라는 프로그램의 출력으로 생성됩니다. 링커는 여러 객체 파일을 링크하여 직접 실행할 수있는 이진 파일을 생성합니다. 이진 실행 파일은 일반적으로 Windows의 .exe 끝나지 만 Unix 운영 체제에는 특별한 접미사가 없습니다.
    예 : foo foo.exe

  5. 라이브러리 : 라이브러리는 컴파일 된 바이너리이지만 실행 파일 자체는 아닙니다 (즉, 라이브러리에 main() 함수가 없습니다). 라이브러리에는 둘 이상의 프로그램에서 사용할 수있는 기능이 있습니다. 라이브러리는 라이브러리의 모든 기능에 대한 프로토 타입을 포함하는 헤더 파일과 함께 제공되어야합니다. 이러한 헤더 파일은 라이브러리를 사용하는 모든 소스 파일에서 참조되어야합니다 (예 : #include <library.h> ). 그런 다음 프로그램을 성공적으로 컴파일 할 수 있도록 링커를 라이브러리에 연결해야합니다. 라이브러리에는 정적 라이브러리와 동적 라이브러리의 두 가지 유형이 있습니다.

    • 정적 라이브러리 : 정적 라이브러리 ( .lib 확장명을 사용하는 DLL 가져 오기 라이브러리 파일 과 혼동하지 말고 POSIX 시스템 용 .a 파일과 Windows 용 .lib 파일)은 프로그램에 정적으로 내장되어 있습니다. 정적 라이브러리는 프로그램이 사용되는 라이브러리 버전을 정확히 알고 있다는 장점이 있습니다. 반면에 사용되는 모든 라이브러리 함수가 포함되어 있기 때문에 실행 파일의 크기가 커집니다.
      예 : libfoo.a foo.lib
    • 동적 라이브러리 : 동적 라이브러리 (대부분의 POSIX 시스템 용 .so 파일, OSX 용 .dylib 및 Windows 용 .dll 파일)는 프로그램에서 런타임에 동적으로 링크됩니다. 하나의 라이브러리 이미지를 여러 프로그램에서 공유 할 수 있기 때문에 이러한 라이브러리를 공유 라이브러리라고도합니다. 동적 라이브러리는 둘 이상의 응용 프로그램이 라이브러리를 사용하는 경우 적은 디스크 공간을 차지할 수 있다는 장점이 있습니다. 또한 실행 파일을 다시 빌드 할 필요없이 라이브러리를 업데이트 (버그 수정) 할 수 있습니다.
      예 : foo.so foo.dylib foo.dll

전처리 기

C 컴파일러가 소스 코드 파일을 컴파일하기 전에 파일은 사전 처리 단계에서 처리됩니다. 이 단계는 별도의 프로그램으로 수행하거나 하나의 실행 파일에 완전히 통합 할 수 있습니다. 어쨌든 컴파일이 시작되기 전에 컴파일러에 의해 자동으로 호출됩니다. 전처리 단계는 텍스트 대체를 적용하여 소스 코드를 다른 소스 코드 또는 번역 단위로 변환합니다. "수정 된"소스 코드 또는 "확장 된"소스 코드라고 생각할 수 있습니다. 확장 된 소스는 파일 시스템에 실제 파일로 존재할 수도 있고, 짧은 시간 동안 메모리에 저장 될 수도 있습니다.

전 처리기 명령은 파운드 기호 ( "#")로 시작합니다. 몇 가지 전처리 기 명령이 있습니다. 가장 중요한 두 가지는 다음과 같습니다.

  1. 정의 :

    #define 은 주로 상수를 정의하는 데 사용됩니다. 예를 들어,

    #define BIGNUM 1000000
    int a = BIGNUM; 
    

    된다

    int a = 1000000;
    

    #define 은 소스 코드 파일의 여러 위치에 상수 값을 명시 적으로 작성하지 않아도되도록이 방법으로 사용됩니다. 이것은 상수 값을 나중에 변경해야 할 경우에 중요합니다. #define 에서 코드 전체에 여러 곳에 흩어져 있어야하는 것보다 버그를 수정하기가 훨씬 쉽습니다.

    #define 은 고급 검색 및 바꾸기뿐이므로 매크로를 선언 할 수도 있습니다. 예를 들면 :

    #define ISTRUE(stm) do{stm = stm ? 1 : 0;}while(0)
    // in the function:
    a = x;
    ISTRUE(a);
    

    다음과 같이됩니다.

    // in the function:
    a = x;
    do {
        a = a ? 1 : 0;
    } while(0);
    

    첫 번째 근사값에서이 효과는 인라인 함수와 거의 동일하지만 선행 처리기는 #define 매크로에 대한 유형 검사를 제공하지 않습니다. 이것은 오류가 발생하기 쉬운 것으로 잘 알려져 있으며 사용에는 큰주의가 필요합니다.

    또한 전처리 기는 아래 설명 된대로 주석을 주석으로 대체합니다.

  2. 다음을 포함합니다 :

    #include 는 소스 코드 파일 외부에서 정의 된 함수 정의에 액세스하는 데 사용됩니다. 예를 들면 :

     #include <stdio.h> 
    

    선행 처리기가 컴파일되기 전에 <stdio.h> 의 내용을 소스 코드 파일의 #include 문에 붙여 넣습니다. #include 는 거의 항상 함수 선언 및 #define 문을 포함하는 파일 인 헤더 파일을 포함하는 데 사용됩니다. 이 경우 printfscanf 와 같은 함수를 사용할 수 있으려면 #include 를 사용합니다.이 함수의 선언은 stdio.h 파일에 있습니다. C 컴파일러는 이전에이 파일에서 선언되거나 정의되지 않은 한 함수를 사용할 수 없도록합니다. #include 문은 C 프로그램에서 이전에 작성한 코드를 다시 사용하는 방법입니다.

  3. 논리 연산 :

    #if defined A || defined B
    variable = another_variable + 1;
    #else
    variable = another_variable * 2;
    #endif
    

    변경됩니다 :

    variable = another_variable + 1;
    

    A 또는 B가 이전에 프로젝트의 어딘가에 정의 된 경우 이것이 사실이 아니라면, 물론 전처리 기는 이렇게 할 것입니다 :

    variable = another_variable * 2;
    

    이것은 종종 다른 시스템에서 실행되거나 다른 컴파일러에서 컴파일되는 코드에 사용됩니다. 전역 정의가 있기 때문에 컴파일러 / 시스템마다 다르므로 이러한 정의를 테스트하고 컴파일러가 항상 컴파일 할 코드 만 사용하게 할 수 있습니다.

  4. 코멘트

    전 처리기는 소스 파일의 모든 주석을 단일 공백으로 대체합니다. 주석은 // 줄 끝까지 또는 여는 /* 및 닫는 */ 주석 괄호의 조합으로 표시됩니다.

컴파일러

C 전처리 기가 모든 헤더 파일을 포함하고 모든 매크로를 확장 한 후에 컴파일러는 프로그램을 컴파일 할 수 있습니다. C 소스 코드를 소스 코드의 바이너리 버전이 들어있는 .o 로 끝나는 파일 인 객체 코드 파일로 변환하면됩니다. 그러나 객체 코드는 직접 실행 가능하지 않습니다. 실행 파일을 만들기 위해, 당신은 또한 있었다 라이브러리의 모든 기능에 대한 코드를 추가 할 필요가 #include (이것은 무엇 인 선언을 포함하여 동일하지 않습니다 파일에 D를 #include 않습니다). 이것은 링커 의 임무입니다.

일반적으로 C 컴파일러를 호출하는 정확한 순서는 사용중인 시스템에 따라 다릅니다. 여기에서는 GCC 컴파일러를 사용하고 있지만 더 많은 컴파일러가 존재한다는 점에 유의해야합니다.

% gcc -Wall -c foo.c

% 는 OS의 명령 프롬프트입니다. 이것은 컴파일러에게 foo.c 파일에서 전 처리기를 실행 한 다음이를 foo.o 라는 오브젝트 코드 파일로 컴파일하도록 지시합니다. -c 옵션은 소스 파일을 오브젝트 파일로 컴파일하지만 링커는 호출하지 않음을 의미합니다. 이 옵션 -c 는 POSIX 시스템 (예 : Linux 또는 macOS)에서 사용할 수 있습니다. 다른 시스템은 다른 구문을 사용할 수 있습니다.

전체 프로그램이 하나의 소스 코드 파일에있는 경우 대신 다음 작업을 수행 할 수 있습니다.

% gcc -Wall foo.c -o foo

이것은 foo.c 에서 프리 프로세서를 실행하고 컴파일 한 다음 링크하여 foo 라는 실행 파일을 작성하도록 컴파일러에 지시합니다. -o 옵션은 행의 다음 단어가 2 진 실행 파일 (프로그램)의 이름임을 나타냅니다. -o 지정하지 않으면 ( gcc foo.c 만 입력하면), 실행 파일은 역사적인 이유로 a.out 으로 명명됩니다.

일반적으로 컴파일러는 .c 파일을 실행 파일로 변환 할 때 네 단계를 거칩니다.

  1. 사전 처리 - .c 파일에서 #include 지시문과 #define 매크로를 텍스트로 확장합니다.
  2. compilation - 프로그램을 어셈블리로 변환합니다 (이 단계에서 -S 옵션을 추가하여 컴파일러를 중지 할 수 있음).
  3. assembly - 어셈블리를 기계 코드로 변환합니다.
  4. 링키지 - 개체 코드를 외부 라이브러리에 연결하여 실행 파일을 만듭니다.

우리가 사용하고있는 컴파일러의 이름은 문맥에 따라 "GNU C 컴파일러"와 "GNU 컴파일러 콜렉션"을 의미하는 GCC이다. 다른 C 컴파일러가 존재합니다. 유닉스 계열 운영체제의 경우, 많은 사람들은 "C 컴파일러"라는 이름의 cc 라는 이름을 가지고 있습니다.이 컴파일러는 종종 다른 컴파일러에 대한 심볼릭 링크입니다. Linux 시스템에서 cc 는 종종 GCC의 별칭입니다. macOS 또는 OS-X에서는 clang을 가리 킵니다.

POSIX 표준은 현재 c99 를 C 컴파일러의 이름으로 요구합니다. 기본적으로 C99 표준을 지원합니다. 이전 버전의 POSIX에서는 c89 를 컴파일러로 사용했습니다. POSIX는 또한이 컴파일러가 위에 사용 된 -c-o 옵션을 이해하도록 요구합니다.


참고 :gcc 예제 모두에있는 -Wall 옵션은 의심스러운 구문에 대한 경고를 출력하도록 컴파일러에 지시합니다. 다른 경고 옵션 (예 : -Wextra 을 추가하는 것도 좋은 생각입니다.

번역 단계

§5.1.1.2 번역 단계에 나열된 C 2011 표준에서 소스 코드를 프로그램 이미지 (예 : 실행 파일)로 변환하면 8 단계의 순서로 나열됩니다.

  1. 소스 파일 입력은 소스 문자 세트에 맵핑됩니다 (필요한 경우). Trigraphs는이 단계에서 대체됩니다.
  2. 연속 줄 ( \ 끝나는 줄)은 다음 줄과 연결됩니다.
  3. 소스 코드는 공백과 사전 처리 토큰으로 파싱됩니다.
  4. 사전 처리기가 적용되어 지시문을 실행하고, 매크로를 확장하고, pragma를 적용합니다. #include 가 가져온 각 소스 파일은 변환 단계 1 - 4 (필요한 경우 재귀 적으로)를 거칩니다. 그런 다음 모든 전 처리기 관련 지시문이 삭제됩니다.
  5. 문자 상수 및 문자열 리터럴의 소스 문자 집합 값은 실행 문자 집합에 매핑됩니다.
  6. 서로 인접한 문자열 리터럴은 연결됩니다.
  7. 소스 코드는 번역 단위를 구성하는 토큰으로 파싱됩니다.
  8. 외부 참조가 해석되고 프로그램 이미지가 형성됩니다.

C 컴파일러의 구현은 여러 단계를 결합 할 수 있지만 결과 이미지는 위의 단계가 위에 나열된 순서대로 개별적으로 발생한 것처럼 동작해야합니다.



Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow