c语言----程序编译(预处理)(代码片段)

4nc414g0n 4nc414g0n     2022-12-13     739

关键词:

下面是从test.c到运行结果的大体过程

编译

1)预编译

gcc test.c -E > test.i
预处理后停止
完成文本操作

  1. 完成头文件包含
  2. #define定义的符号和宏的替换
  3. 去除注释

2)编译

gcc test.i -S
生成test.s的文件
把C语言代码转化为汇编代码
编译部分深入学习编译原理

  1. 语法分析
  2. 词法分析
  3. 语义分析
  1. 符号汇总

    1).把c程序所有的全局符号汇总(add.c中的全局符号是Addtest.c中全局符号为mainAdd)

3)汇编

PE ELF COFF概念

PEPortable Executable)意为可移植的可执行的文件,常见的EXE、DLL、OCX、SYS、COM都是PE格式文件,微软Windows操作系统上的程序文件(可能是间接被执行,如DLL)


ELFExecutable and Linkable Format)意为可执行与可链接格式,一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件,是Linux的主要可执行文件格式。,用来取代COFF
ELF文件由4部分组成,分别是ELF头(ELF header)、程序头表(Program header table)、节(Section)和节头表(Section header table)。实际上,一个文件中不一定包含全部内容,而且它们的位置也未必如同所示这样安排,只有ELF头的位置是固定的,其余各部分的位置、大小等信息由ELF头中的各项值来决定

gcc test.s -c
生成test.o文件,为ELF格式(类似与Windows下的.obj目标文件,为COFF格式)
把汇编代码转化为二进制指令(机器指令)

readelf -s add.oreadelf -s test.o分别查看汇总的全局符号
将编译中汇总的全局符号分别生成符号表:符号|地址**(分别是test.cadd.c)
| Add | 0x0000 | | | Add | 0x1008 |
| main| 0x1004 | |

链接

把多个目标文件(.o文件)进行链接
test.o+add.o->a.out(.out可执行文件也是ELF格式)

  1. 合并段表(每个ELF文件相当于一个段表)
  1. 符号表合并和重定位
    | Add | 0x1008 |
    | main| 0x1004 |
    Add地址有效相当于通过地址找到函数

运行环境

  1. 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须
    由手工安排,也可能是通过可执行代码置入只读内存来完成。
  2. 程序的执行便开始。接着便调用main函数。
  3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同
    时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
  4. 终止程序。正常终止main函数;也有可能是意外终止。

预定义符号(注意是两根杠

_ _ FILE_ _ :进行编译的源文
_ _ LINE _ _ :文件当前的行号
_ _ DATE _ _ :文件被编译的日期
_ _ TIME _ _ :文件被编译的时间
_ _ STDC _ _ :如果编译器遵循ANSI C,其值为1,否则未定义
_ _ FUNCTION _ _ :函数名
_ _ VA_ARGS _ _ :是系统预定义宏,被自动替换为参数列表


#define

预处理的替换,只会做最简单的浅层替换,而不会去考虑这个字符串是从哪里来的

1)#和##

字符串有自动连接的特点,如打印hello world

char* p = "hello ""world\\n";
printf("hello"," world\\n");
printf("%s", p);

只有当字符串作为宏参数的时候才可以把字符串放在字符串中

#define PRINT(FORMAT, VALUE)\\
printf("the value is "FORMAT"\\n", VALUE);
PRINT("%d", 10);

# 的用法:
# VALUE 会预处理为 " VALUE "
打印 the value of i+3 is 13

int i = 10;
#define PRINT(FORMAT, VALUE)\\
printf("the value of " #VALUE "is "FORMAT "\\n", VALUE);
PRINT("%d", i+3);

## 的用法:
##可以把位于它两边的符号合成,从而产生新的符号(词法层次),它允许宏定义从分离的文本片段创建标识符
sum##num += value;带入值变为sum5 += 10;

#define ADD_TO_SUM(num, value)
sum##num += value;
ADD_TO_SUM(5, 10);

2)#define

注意:

  1. 宏参数和#define 定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归。(语法规定,当一个宏遇到自己时,就停止展开)
  2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索

#define CASE break;case //在写case语句的时候自动把 break写上


#define LOG( format, ... ) printf( format, _ _VA_ARGS_ _ )
LOG( "%s %d", str, count );
//变参宏


在define定义标识符时建议不要加上‘;’
当你不小心加上一个‘;’号时由于没有括号else会匹配最近的;语句,如下例子

#define MAX 1000;
if(condition)
	max = MAX;
else
	max = 0;

在宏定义上加上两个括号 #define SQUARE(x) (x) * (x)以防出现以下

#define SQUARE( x ) x * x
int a = 5;
printf("%d\\n" ,SQUARE( a + 1) );

替换文本时,参数x被替换成a + 1,所以这条语句实际上变成了: printf ("%d\\n",a + 1 * a + 1 ); 所以他会打印11而不是想象的36

外面也要加上括号 #define SQUARE(x) ((x) + (x))以防出现以下

#define DOUBLE(x) (x) + (x)
int a = 5;
printf("%d\\n" ,10 * DOUBLE(a));

乘法运算先于宏定义的加法,所以出现了printf ("%d\\n",10 * (5) + (5));打印55而非想象的100

宏参数的prescan
因为ADDPARAM( 1 ) 是作为PARAM的宏参数,所以先将ADDPARAM( 1 )展开为INT_1,然后再将INT_1放进PARAM

#define PARAM( x ) x
#define ADDPARAM( x ) INT_##x
PARAM( ADDPARAM( 1 ) );

如果PARAM宏里对宏参数使用了#或##,那么宏参数不会被展开 如下:将被展开为"ADDPARAM( 1 )

#define PARAM( x ) #x
#define ADDPARAM( x ) INT_##x
PARAM( ADDPARAM( 1 ) );

3)宏与函数对比

宏的优点
宏的参数可以出现类型,但是函数做不到

#define MALLOC(num, type) (type *)malloc(num * sizeof(type))
//使用
MALLOC(10, int);//类型作为参数
//预处理器替换之后:
(int *)malloc(10 * sizeof(int));

#define MAX(a, b) ((a)>(b)?(a):(b))
对于此代码功能选用宏的原因

  1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
    函数转化为汇编代码会更多
  2. 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之宏可以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关的

宏的缺点

  1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
  2. 宏是没法调试的
  3. 宏由于类型无关,也就不够严谨。
  4. 宏可能会带来运算符优先级的问题,导致程容易出现错
  5. **带副作用的宏参数**
#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
x = 5;
y = 8;
z = MAX(x++, y++);
printf("x=%d y=%d z=%d\\n", x, y, z);

z = ( (x++) > (y++) ? (x++) : (y++));
x=6 y=10 z=9


命令行定义

当我们根据同一个源文件要编译出不同的一个程序的不同版本的时候,可以用命令行定义
如我们在程序中定义了一个 int array [ARRAY_SIZE];
gcc -D ARRAY_SIZE=10 programe.c(在命令行中输入ARRAY_SIZE的值)

条件编译

调试性的代码,不想删除,但不想执行,我们可以选择性的编译(不同于注释)

#if 常量表达式
//...
#endif
//常量表达式由预处理器求值。

如:

#define __DEBUG__ 1
#if __DEBUG__
//..
#endif

多个分支的条件编译

#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif

判断是否被定义

#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol

嵌套指令

#if defined(OS_UNIX)
#ifdef OPTION1
unix_version_option1();
#endif
#ifdef OPTION2
unix_version_option2();
#endif
#elif defined(OS_MSDOS)
#ifdef OPTION2
msdos_version_option2();
#endif
#endif

文件包含

#include "filename"
先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。 如果找不到就提示编译错误。
linux环境的标准头文件的路径:/usr/include

#include <filename>
查找头文件直接去标准路径下去查找,如果找不到就提示编译错误

嵌套包含
避免重复包含我们可以使用:

  1. #ifndef _ _ TEST_H _ _
    #define _ _ TEST_H _ _
    //头文件的内容
    #endif //_ _ TEST_H_ _
  2. #pragma once

其他预处理指令

#error token-string
token-string:用户自定义的错误消息
当预处理器预处理到#error命令时将停止编译并输出用户自定义的错误消息

#line digit-sequence "filename"
digit-sequence:_ _ LINE _ _
"filename":_ _ FILE _ 的内容
命令#line改变
_ LINE _ _ FILE _ _的内容,它们是在编译程序中预先定义的标识符
The translator uses the line number and filename to determine the values of the predefined macros FILE and LINE

#pragma(详见MSDN)
alloc_text ,comment ,init_seg1 ,optimize
auto_inline ,component, inline_depth ,pack
bss_seg ,data_seg, inline_recursion ,pointers_to_members1
check_stack ,function ,intrinsic ,setlocale
code_seg, hdrstop ,message, vtordisp1
const_seg ,include_alias ,once ,warning

梦开始的地方——c语言预处理+编译过程(代码片段)

文章目录C语言程序的编译(预处理)1.编译和链接1)编译的几个阶段预编译阶段编译阶段汇编阶段2)链接2.预处理1)预定义符号2)#define3)#和##4)带副作用的宏参数5)宏和函数对比3.常见预处理命令1)#undef2)命令行定义3)条件编译4)文件包含5... 查看详情

gcc编译流程(代码片段)

...在编译一个C语言程序时需要经过以下4步:将C语言源程序预处理,生成.i文件。预处理后的.i文件编译成为汇编语言,生成.s文件。将汇编语言文件经过汇编,生成目标文件.o文件。将各个模块的.o文件链接起来生成一个可执行程... 查看详情

c语言编译过程,满满的干货!!!(代码片段)

程序环境和预处理一、程序翻译和运行环境二、预处理详解1.预定义符号2.define定义宏3.#和##的区别4.宏和函数好坏比较5.命名约定6.头文件中<>和""区别7.条件编译一、程序翻译和运行环境翻译环境:在翻译环境中ÿ... 查看详情

c语言-预处理(代码片段)

C语言中编译流程:预处理编译汇编链接预定义符号__FILE__进行编译的源文件__LINE__文件当前的行号__DATE__文件被编译的日期__TIME__文件被编译的时间__STDC__如果编译器遵守C语言标准,其值为1,否则未定义或为0__DATE__和__... 查看详情

gcc编译流程及常用编辑命令(代码片段)

...在编译一个C语言程序时需要经过以下4步:将C语言源程序预处理,生成.i文件。预处理后的.i文件编译成为汇编语言,生成.s文件。将汇编语言文件经过汇编,生成目标文件.o文件。将各个模块的.o文件链接起来生成一个可执行程... 查看详情

c:初识(代码片段)

...解释一下每一行代码#include<stdio.h>这一行代码是一条C预处理指令(preprocessordirective),是什么是预处理?就是编译器在编译前的准备工作,即预处理。stdio.h文件包含了供编译器使用的输入和输出的函数。通常,在C程序顶部... 查看详情

c语言学习笔记(19)程序环境和预处理(代码片段)

...的翻译环境和执行环境翻译环境(编译+链接)预处理编译汇编运行环境预处理预定义符号#define#define定义标识符#define定义宏#define替换规则#和##带副作用的宏参数宏和函数的对比#undef命令行定义条件编译文件包含头文... 查看详情

c语言重点难点精讲c语言预处理(代码片段)

文章目录一:C/C++程序程序编译过程(1)预处理(2)编译(3)汇编(4)链接二:宏定义(1)数值宏常量(2)字符串宏常量(3)使用宏充当注释( 查看详情

嵌入式工程师面试题集-c语言(代码片段)

...1.什么是预编译,何时需要预编译答:预编译又称预处理,就是做些代码文本的替换工作。#开头的指令,比如拷贝#include包含的文件代码,#define宏定义的替换,条件编译等,就是为编译做的预备工作的阶段... 查看详情

c语言中程序的编译(预处理操作)+链接详解(详细介绍程序预编译过程)(代码片段)

...目录1.前言2.翻译环境和运行环境2.1翻译环境2.2运行环境3.预处理详解3.1预定义符号3.2#define定义的标识符常量和宏3.2.1#define定义的标识符常量3.2.2#define定义的宏3.2.3#define替换规则3.2.4#和##3.2.5带副作用的宏参数3.3宏和函数的对比4.... 查看详情

c语言基础(代码片段)

...序。最常用的编译器是gcc(mac上xcode就可以)程序结构#include预处理器指令,类似于import,主要用于告诉编译器,我们要引入什么。.h结尾的是头文件,头文件中一般是定义的结构体和变量#include<stdio 查看详情

c++常用命令行开发工具(linux)(代码片段)

1、简介编译的四个阶段:预处理(扩展各个宏与文件)、编译(得到汇编代码)、汇编(得到机器码)、链接(得到可执行文件)预处理:编译处理宏定义等宏命令(eg:#define)——生成后缀为“.i”的文件  编译:将预处理... 查看详情

gcc介绍(代码片段)

...+源文件.mObject-C源文件.i经过与处理后的C源文件.ii经过预处理后的C++源文件.s.S汇编语言源文件.h预处理文件(头文件).o目标文件.a存档文件2.gcc编译程序的流程源文件(hello.c)——>预处理(预处理器)——>编译(编译器)—... 查看详情

c语言程序设计ii—第十一周教学(代码片段)

...识点:多个函数构成的程序结构。10.2递归函数。10.3编译预处理概念,包括文件、宏的内容。10.4多文件模块的学生信息系统,展示大程序的构成。  教学目标:要求学生能够对相对复杂的问题,合理定义程序的多函数结构;能... 查看详情

c语言-预处理(#define#if...)(代码片段)

1.区分预处理代码在C语言程序里,出现的#开头的代码段都属于预处理。预处理:是在程序编译阶段就执行的代码段。比如:包含头文件的的代码#include<stdio.h>#include<stdlib.h>#include<string.h>下面列出C语言里常用的... 查看详情

c语言-预处理(#define#if...)(代码片段)

1.区分预处理代码在C语言程序里,出现的#开头的代码段都属于预处理。预处理:是在程序编译阶段就执行的代码段。比如:包含头文件的的代码#include<stdio.h>#include<stdlib.h>#include<string.h>下面列出C语言里常用的... 查看详情

c语言进阶学习笔记——程序环境和预处理(代码片段)

...翻译环境和执行环境详解编译+链接翻译环境运行环境预处理详解预定义符号#define#define定义标识符#define定义宏#define替换规则#和##带副作用的宏参数宏和函数宏和函数的对比命名约定#undef命令行定义条件编译文件包含头文件被... 查看详情

基础复习gcc构造可执行程序的过程(代码片段)

假设源文件为:tmp.c预处理阶段:编译器驱动程序调用C语言预处理器(cpp),生成ASCII中间文件(.i)gcc选项为“-E”gcc-Etmp.c-otmp.i编译阶段:驱动程序运行C编译器(cc1),生成ASCII汇编语言文件(.s)gcc选项为"-S"gcc-Stmp.i-otmp.s汇编阶段:驱动程序... 查看详情