C语言编译
Posted 下一站不是永远
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言编译相关的知识,希望对你有一定的参考价值。
前言
我们在各自的电脑上写下代码,得明白我们代码究竟是如何产生的。
我们的代码会经过这4个环节,从而形成最终文件,c语言作为编译语言,用来向计算机发出指令。让程序员能够准确地定义计算机所需要使用的数据,并精确地定义在不同情况下所应当采取的行动。
下面让我们一起学习C语言产生过程
本章重点
程序的翻译环境和执行环境
预定义符号的介绍
预处理指令#define
宏和函数的对比
预处理操作符#和##的介绍
条件编译
offsetof的实现
正文开始
1.程序的翻译环境和执行环境
在ANSI C中任何一种的实现中,存在两个不同的环境。
第一种是翻译环境,在这个指令中源代码被转换为可执行的机器指令,第二种是执行环境,实际执行代码。
翻译环境
组成一个程序的每个源文件通过编译过程分别转换成目标代码(objectcode)。
每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。
链接器同时也会引入标准C函数库中任何被该程序所用到的函数,
而且它可以搜索程序员个人的程序库将其需要的函数也链接到程序中。
实例
#include<stdio.h>
int main()
int i = 0;
int arr[10] = 0 ;
for (i = 0; i < 10; i++)
arr[i] = i;
for (i = 0; i < 10; i++)
printf("%d ", arr[i]);
return 0;
执行环境
1.预处理选项gcc-Etest.c-o test.i预处理完成之后就停下来,
预处理之后产生的结果都放在testi文件中。
2.编译选项gcc-stestc编译完成之后就停下来,结果保存在test.s中。
3汇编gcc-ctestc汇编完成之后就停下来,结果保存在test.o中
2.预定义符号介绍
预处理详解
预定义符号
__FTLE__ 进行编译的源文件
__LINE__ 文件当前的序号
__DATE__ 文件被编译的日期
__TIME__ 文件被编译的时间
__STDC__ 如果编译器遵循ANSI 得到的值是1,否则为未定义
实例
printf("file:%s line:%d\\n",__FILE__.__LINE__);
3.预处理指令#define
#define定义标识符
语法
#define name stuff
实例
#define MAX 1000
#define MUL(a,b) (a*b)
int main (void)
int c;
c=MUL(3,5+1);
printf("c=%d\\n",c);
return 0;
4.宏和函数的对比
宏的申明方式
#define name(parament-list)stuff其中
的parament-list是一个由逗号隔开的符号表,它们可能出现在 stuff中。
注意:参数列表的左括号必须与name紧邻。
如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。
例如
#define NUM()X*X
这个宏接收一个参数x如果在上述声明之后,你把
NUM(5);
置于程序中,预处理器就会用下面这个表达式替换上面的表达式:
5*5
实例1
#define NUM(x) x*x
int main()
int ret = NUM(5);
printf("%d\\n", ret);
return 0;
实例2
#define NUM(x) (x)*(x)
int main()
//int ret = NUM(5);
int ret = NUM(5 + 1);
printf("%d\\n", ret);
return 0;
通过上面两个方式我们可以知道
所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号。
避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。
#define 的替换规则
在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
替换文本随后被插入到程序中原来文本的位置。对千宏,参数名被他们的值替换。
最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
注意:
1.宏参数和#define 定义中可以出现其他#define定义的变量,但是对于宏,不能出现递归。
2.当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
5.预处理操作符#和##的介绍
#和##
如何把参数插入到字符串中
实例1
#include <stdio.h>
int main()
char* p = "hello";
//printf("hello\\n");
printf("%s", p);
return 0;
实例2
#define PRINT(x) printf("the value of "#x" is %d\\n",x)
int main()
int a = 10;
int b = 20;
PRINT(a);
PRINT(b);
return 0;
实例3
#include<stdio.h>
#define MAX(x,y) ((x)>(y)?(x):(y))
int main()
int a = 10;
int b = 20;
int max = MAX(a++, b++);
//int max=MAX ((a++)>(b++)?(a++):(b++));
printf("%d\\n", max);
printf("%d\\n", a);
printf("%d\\n", b);
return 0;
宏和函数对比
宏通常被应用于执行简单的运算。比如在两个数中找出较大的一个。
#define MAx(a,b)((a)>(b)?(a):(b))
那为什么不用函数来完成这个任务?原因有二
1.用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
2.更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关的。
当然和宏相比函数也有劣势的地方!
1.每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
2.宏是没法调试的。
3.宏由干类型无关,也就不够严谨。
4.宏可能会带来运算符优先级的问题,导致程容易出现错。
6.条件编译
在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。
比如说:
调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译。
文件包含
#inc1ude指令可以使另外一个文件被编译。就像它实际出现于#inc1ude指令的地方一样。这种替换的方式很简单:预处理器先删除这条指令,并用包含文件的内容替换。这样一个源文件被包含10次,那就实际被编译10次。
7.offsetof的实现
#include<stdio.h>
#include<stddef.h>
struct S
char c1;
int a;
char c2;
;
int main()
printf("%d\\n", offsetof(struct S, c1));
printf("%d\\n", offsetof(struct S, a));
printf("%d\\n", offsetof(struct S, c2));
return 0;
问题分析
如下图 因为9不是4的倍数,所以总共应该是比9大并且是4的倍数,所以是12
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
offsetof的实现
#include<stdio.h>
#include<stddef.h>
#define OFFSETOF(name,member) (int)&(((name*)0)->member)
struct S
char c1;
int a;
char c2;
;
int main()
printf("%d\\n", OFFSETOF(struct S, c1));
printf("%d\\n", OFFSETOF(struct S, a));
printf("%d\\n", OFFSETOF(struct S, c2));
return 0;
8.下一站
小草因坚持而生存,失败因坚持而胜利,人生因坚持而快乐……坚持不懈也是一种美。
本章完
加油!坚持!
《数据结构》走起!
以上是关于C语言编译的主要内容,如果未能解决你的问题,请参考以下文章