C 언어 컴파일 프로세스 헤더 파일 처리

헤더 파일 처리

 

컴파일러는 소스 프로그램 (문자 스트림)을 읽고, 어휘와 문법을 분석하고, 고급 언어 명령을 기능적으로 동등한 어셈블리 코드로 변환 한 다음 어셈블리 프로그램을 기계어로 변환하고 운영 체제에 따라 변환합니다. 파일 형식을 실행하려면 실행 가능한 프로그램을 생성하기위한 링크가 필요합니다.
C 소스 프로그램 헤더 파일-> 미리 컴파일 된 처리 (cpp)-> 컴파일러 자체-> 최적화 된 프로그램-> 어셈블러-> 링커-> 실행 파일
1. 컴파일 전처리
c 소스 읽기 의사 명령어 (#로 시작하는 명령어) 및 특수 기호를 처리하는 프로그램입니다.
의사 명령어는 주로 다음 4 가지 측면
(1) #define Name TokenString, #undef 등과 같은 매크로 정의 명령어를 포함합니다. 이전 의사 명령어의 경우 사전 컴파일이 수행해야하는 작업은 프로그램의 모든 이름을 TokenString으로 바꾸는 것이지만 문자열 상수로서의 이름은 대체되지 않습니다. 후자의 경우 특정 매크로의 정의가 취소되므로 문자열의 발생이 더 이상 대체되지 않습니다.

(2) #ifdef, #ifndef, #else, #elif, #endif 등과 같은 조건부 컴파일 명령. 이러한 의사 명령어의 도입으로 프로그래머는 다른 매크로를 정의하여 컴파일러가 처리 할 코드를 결정할 수 있습니다. 사전 컴파일러는 관련 파일에 따라 불필요한 코드를 필터링합니다.

(3) 헤더 파일에는 #include "FileName"또는 #include <FileName> 등과 같은 지침이 포함되어 있습니다. 많은 수의 매크로 (가장 일반적인 것은 문자 상수)는 일반적으로 헤더 파일의 의사 명령어 #define에 의해 정의되며 다양한 외부 기호의 선언도 포함합니다. 헤더 파일을 사용하는 주요 목적은 여러 다른 C 소스 프로그램에서 특정 정의를 사용할 수 있도록하는 것입니다. 이러한 정의를 사용해야하는 C 소스 프로그램에서는이 파일에서 이러한 정의를 반복하는 대신 #include 문을 추가하기 만하면됩니다. 사전 컴파일러는 헤더 파일의 모든 정의를 컴파일러가 처리 할 수 ​​있도록 생성하는 출력 파일에 추가합니다.

c 소스 프로그램에 포함 된 헤더 파일은 시스템에서 제공 할 수 있으며 이러한 헤더 파일은 일반적으로 / usr / include 디렉토리에 있습니다. # 각괄호 (<>)를 사용하려면 프로그램에 포함시킵니다. 또한 개발자가 자신의 헤더 파일을 정의 할 수도 있습니다. 이러한 파일은 일반적으로 c 소스 프로그램과 동일한 디렉토리에 배치됩니다. 이때 #include에 큰 따옴표 ( "")를 사용해야합니다.

(4) 특수 기호, 사전 컴파일러는 일부 특수 기호를 인식 할 수 있습니다. 예를 들어, 소스 프로그램에 나타나는 LINE 식별자는 현재 행 번호 (10 진수)로 해석되고 FILE은 현재 컴파일중인 C 소스 프로그램의 이름으로 해석됩니다. 사전 컴파일 된 프로그램은 소스 프로그램에 나타나는 이러한 문자열을 적절한 값으로 대체합니다.


미리 컴파일 된 프로그램이 수행하는 것은 기본적으로 소스 프로그램의 "대체"입니다. 이 대체 후에는 매크로 정의, 조건부 컴파일 명령 및 특수 기호가없는 출력 파일이 생성됩니다. 이 파일의 의미는 전처리가없는 소스 파일과 동일하지만 내용이 다릅니다. 다음 단계에서이 출력 파일은 컴파일러의 출력으로 기계 명령어로 변환됩니다.
2. 컴파일 단계

사전 컴파일로 얻은 출력 파일에는 상수 만 있습니다. 숫자, 문자열, 변수 및 C 언어 키워드 (예 : main, if, else, for, while, {,}, +,-, *, / 등)의 정의. 프리 컴파일러가해야 할 일은 어휘 분석과 문법 분석을 통과하는 것입니다. 모든 명령어가 문법 규칙을 준수하는지 확인한 후 동등한 중간 코드 표현 또는 어셈블리 코드로 변환됩니다.
3. 최적화 단계
최적화 처리는 컴파일 시스템에서 비교적 어려운 기술입니다. 관련된 문제는 컴파일 기술 자체와 관련이있을뿐만 아니라 기계의 하드웨어 환경과도 큰 관계가 있습니다. 최적화 부분은 중간 코드의 최적화입니다. 이 최적화는 특정 컴퓨터에 의존하지 않습니다. 다른 최적화는 주로 대상 코드 생성을위한 것입니다. 위의 그림에서는보다 일반적인 표현 인 컴파일러 뒤에 최적화 단계를 배치했습니다.

이전 최적화의 경우 주요 작업은 공통 표현식 삭제, 루프 최적화 (코드 아웃소싱, 강도 약화, 루프 제어 조건 변경, 알려진 수량 병합 등), 전파 복사 및 불필요한 할당 삭제 등입니다.

후자의 최적화 유형은 기계의 하드웨어 구조와 밀접한 관련이 있습니다. 가장 중요한 고려 사항은 기계의 하드웨어 레지스터에 저장된 변수 값을 최대한 활용하여 메모리 액세스 수를 줄이는 방법입니다. 또한 기계 하드웨어 실행 명령 (파이프 라인, RISC, CISC, VLIW 등)의 특성에 따라 명령을 조정하여 대상 코드를 짧게 만들고 실행 효율성을 높이는 방법도 중요한 연구 주제입니다.

최적화 된 어셈블리 코드는 어셈블러에 의해 어셈블되고 기계에서 실행되기 전에 해당 기계 명령어로 변환되어야합니다.
4. 어셈블리 프로세스

어셈블리 프로세스는 실제로 어셈블리 언어 코드를 대상 기계 명령어로 변환하는 프로세스를 말합니다. 번역 시스템에서 처리하는 각 C 언어 소스 프로그램에 대해이 과정을 통해 최종적으로 해당 대상 파일을 얻습니다. 대상 파일에 저장되는 것은 소스 프로그램에 해당하는 대상의 기계어 코드입니다.

대상 파일은 세그먼트로 구성됩니다. 일반적으로 개체 파일에는 최소한 두 개의 섹션이 있습니다.

코드 섹션이 섹션에는 주로 프로그램 명령어가 포함되어 있습니다. 이 섹션은 일반적으로 읽고 실행할 수 있지만 일반적으로 쓸 수는 없습니다. 

데이터 세그먼트는 주로 프로그램에서 사용되는 다양한 전역 변수 또는 정적 데이터를 저장합니다. 일반적으로 데이터 세그먼트는 읽기, 쓰기 및 실행 가능합니다. 

UNIX 환경에는 세 가지 주요 유형의 개체 파일이 있습니다.

(1) 재배치 가능 파일에는 실행 파일 또는 공유 개체 파일을 만들기 위해 다른 개체 파일을 연결하는 데 적합한 코드와 데이터가 포함되어 있습니다.

(2) 공유 객체 파일이 파일은 두 가지 컨텍스트에서 링크하기에 적합한 코드와 데이터를 저장합니다. 첫 번째는 링커가 다른 재배치 가능 파일 및 공유 개체 파일과 함께이를 처리하여 다른 개체 파일을 만들 수 있다는 것입니다. 두 번째는 동적 링커가 다른 실행 파일 및 기타 공유 개체 파일과 함께이를 사용한다는 것입니다. 함께 결합하여 프로세스 이미지를 만듭니다.

(3) 실행 파일 운영 체제에서 생성 한 프로세스에서 실행할 수있는 파일을 포함합니다.

어셈블러가 생성하는 것은 실제로 첫 번째 유형의 개체 파일입니다. 후자의 경우에는 다른 처리가 필요하며 이것이 링커의 작업입니다.

5. 링크 프로그램

어셈블러에서 생성 한 오브젝트 파일은 바로 실행할 수 없으며 해결되지 않은 문제가 많을 수 있습니다. 예를 들어, 소스 파일의 함수는 다른 소스 파일 (예 : 변수 또는 함수 호출 등)에 정의 된 기호를 참조 할 수 있고 라이브러리 파일의 함수는 프로그램에서 호출 될 수 있습니다. 이러한 모든 문제는 링크 프로그램의 처리로만 해결할 수 있습니다.

링커의 주요 작업은 관련 개체 파일을 서로 연결하는 것입니다. 즉, 한 파일에서 참조되는 심볼을 다른 파일의 심볼 정의와 연결하여 이러한 모든 개체 파일이 운영 체제 설치가되도록합니다. 통일 된 전체 실행으로.

개발자가 지정한 동일한 라이브러리 함수의 서로 다른 연결 방법에 따라 연결 프로세스는 두 가지 유형으로 나눌 수 있습니다.

(1) 정적 연결이 연결 모드에서는 함수의 코드가 위치하는 정적 연결 라이브러리에서 최종 파일로 복사됩니다. 실행 가능한 프로그램. 이런 식으로 프로그램이 실행될 때 이러한 코드가 프로세스의 가상 주소 공간에로드됩니다. 정적 링크 라이브러리는 실제로 개체 파일의 모음이며, 각 파일에는 라이브러리의 관련 함수 코드 그룹이 하나 또는 포함됩니다.

(2) 동적 링크 이러한 방식으로 함수의 코드는 동적 링크 라이브러리 또는 공유 객체라는 오브젝트 파일에 배치됩니다. 이때 링커는 공유 객체의 이름과 소량의 기타 등록 정보를 최종 실행 프로그램에 기록합니다. 이 실행 파일이 실행되면 동적 링크 라이브러리의 전체 내용이 런타임에 해당 프로세스의 가상 주소 공간에 매핑됩니다. 동적 링크 프로그램은 실행 프로그램에 기록 된 정보에 따라 해당 기능 코드를 찾습니다.

실행 파일의 함수 호출의 경우 동적 연결 또는 정적 연결 방법을 각각 사용할 수 있습니다. 동적 링크를 사용하면 최종 실행 파일이 더 짧아지고 공유 객체가 여러 프로세스에서 사용되는 경우 일부 메모리를 절약 할 수 있습니다. 공유 객체 코드의 사본 하나만 메모리에 저장하면되기 때문입니다. 그러나 동적 연결을 사용하는 것이 정적 연결을 사용하는 것보다 반드시 우월하다는 것은 아닙니다. 경우에 따라 동적 연결로 인해 성능이 손상 될 수 있습니다.



Makefile 컴파일

makefile是用于自动编译和链接的,一个工程有很多文件组成,每一个文件的改变都会导致工程的重新链接-----

        但是不是所有的文件都需要重新编译,makefile能够纪录文件的信息,决定在链接的时候需要重新编译哪些文件!

        

        在unix系统下,makefile是与make命令配合使用的。
 
举个例子吧,我现在有main.c 、window.c 、model.c 、data.c 4个.c文件和window.h 、model.h 、data.h 3个.h文件。

                    main.c是主程序,里面有main()函数。其他的都是模块。

                    

                    如果要生成最终的可执行文件,要做以下步骤:

                    1、分别编译window.c 、model.c 、data.c 、main.c ,将会得到3个目标文件:window.o 、model.o 、data.o 、main.o

                    2、把这4个.o (在windows下就是.obj)文件链接起来,得到main.out(在windows下就是main.exe)。

                    

                    那么这些文件就要有逻辑关系,否则编译器不知道怎么编译。

                    

                    all:main.out

                    main.out:main.o window.o model.o data.o

                    gcc -o main.out main.o window.o model.o data.o

                    

                    #上面的意思是说:

                    #all:main.out

                    如果想要编译所有:make all,那么将会生成main.out可执行文件。

                    

                    #main.out:main.o window.o model.o data.o

                    而要生成这个main.out,需要依赖main.o,window.o,model.o,data.o 4个文件。

                    

                    #    gcc -o main.out main.o window.o model.o data.o

                    这句是调用编译器编译,vc用的是cl。变异的时候可以加上很多的参数、定义的宏、链接库路径等。

                    

                    当然,还没有完呢,这些main.out依赖的这些 .o 怎么来的?

                    

                    window.o:window.c window.h

                    gcc -c window.c

                    

                    model.o:model.c model.h

                    gcc -c model.c

                    

                    data.o:data.c data.h

                    gcc -c data.c

                    

                    上面的-c参数是指定编译器编译出一个.o文件就可以了,不要再寻找main()函数做链接工作。

                    

                    这些和到一起,就是一个makefile,当然这些功能还太少,可以加上很多别的项目。但宗旨就是:

                    让编译器知道要编译一个文件需要依赖其他的哪些文件。当那些依赖文件有了改变,编译器会自动的发现最终的生成文件已经过时,

                    而重新编译相应的模块。

                    

                    现在的VC++真是太好了,不用一个字一个字的去敲Makefile 了。

추천

출처blog.csdn.net/geggegeda/article/details/4205977