目录
一:程序的翻译环境和执行环境
二:编译,链接
1.翻译环境
每一个源文件都会生成一个与之对应的目标文件:
2.编译的阶段
1. 预处理 选项 gcc -E test.c -o test.i
预处理完成之后就停下来,预处理之后产生的结果都放在 test.i 文件中。2. 编译 选项 gcc - S test.c编译完成之后就停下来,结果保存在 test.s 中。3. 汇编 gcc - c test.c汇编完成之后就停下来,结果保存在 test.o 中。
预处理中 .2> .3> 的效果:
符号表:
3.运行环境
程序执行的过程:
三:预处理
1.预定义符号
__FILE__ //进行编译的源文件__LINE__ //文件当前的行号__DATE__ //文件被编译的日期__TIME__ //文件被编译的时间__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
#include<stdio.h>
int main()
{
int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d----%s,%s,%s,line=%d\n", arr[i], __FILE__, __DATE__, __TIME__, __LINE__);
}
return 0;
}
编译器在代码翻译的时候,会对函数和变量名重命名
在C语言中,一般为在前面加上_
2.#define
<1>: #define 定义标识符
语法:#define name stuff如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。#define DEBUG_PRINT printf("file:%s\tline:%d\t \date:%s\ttime:%s\n" ,\__FILE__,__LINE__ , \__DATE__,__TIME__ )
#define 定义语句后面加上 ;(分号)
会产生如下情况:
<2>: #define 定义宏
#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义
#define 仅为替换作用,在使用时,尽可能增添括号
与函数对比:
<3>: #define 替换规则
在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
<4>: # 和 ##
#
写代码实现输入数值,输出与之对应的 the value of is 对应的格式 输入的数值。
在C语言中,字符串会自动连接在一起:
//写代码实现输入数值,输出与之对应的 the value of is 对应的格式 输入的数值
#define print_format(num, format) \
printf("the value of "#num " is "format,num)
int main()
{
int a = 10;
print_format(a,"%d\n");
// printf("the value of is %d\n",a);
int b = 20;
print_format(b,"%d\n");
//printf("the value of is %d\n",b);
double c = 10.0;
print_format(c,"%lf\n");
//printf("the value of is %lf\n",c);
return 0;
}
##
#include<stdio.h>
int catdog = 1011;
#define SUM(x,y) x##y
//将 x 和 y 合在一起
int main()
{
printf("%d\n",SUM(cat,dog));
return 0;
}
<5>: 宏参数的副作用
当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。
int a = 10;
int b = a + 1; // b = 11 , a =10
int c = ++a; // b = 11 , a = 11 存在副作用,改变了a原有的值。
示例:
#include<stdio.h>
#define MAX(x,y) ((x)>(y)?(x):(y))
int main()
{
int a = 10;
int b = 20;
int c = MAX(a++,b++);
printf("%d\n",a);
printf("%d\n",b);
printf("%d\n",c);
return 0;
}
<6>: 宏与函数的对比
写代码,得出两数之间的较大值:(函数)(宏)两种方式
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
//函数
int Max(int x, int y)
{
return x > y ? x : y;
}
//宏
#define MAX(x,y) ((x)>(y)?(x):(y))
int main()
{
int a = 0;
int b = 0;
//输入
scanf("%d %d", &a, &b);
int n = Max(a, b);
printf("%d\n", n);
int m = MAX(a, b);
printf("%d\n", m);
return 0;
}
效果为:
此时,宏的方式比函数的方式更优,原因为:
宏的缺点:
1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序 的长度。2. 宏是没法调试的。3. 宏由于类型无关,也就不够严谨。4. 宏可能会带来运算符优先级的问题,导致程容易出现错。
宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。
#include<stdio.h>
#include<errno.h>
#include<stdlib.h>
#include<string.h>
#define MALLOC(num,type) (type*)malloc(num*sizeof(type))
int main()
{
/*int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
//....
}*/
//开辟空间
int* pa = MALLOC(20, int);
if (pa == NULL)
{
printf("malloc: %s\n", strerror(errno));
return 1;
}
else
{
//使用
}
//释放空间
free(pa);
pa = NULL;
return 0;
}
效果为:
属性 | #define定义宏 | 函数 |
---|---|---|
代码长度 |
每次使用时,宏代码都会被插入到程中。除了非常 小的宏之外,程序的长度会大幅度增长
|
函数代码只出现于一个地方;每次使用这个函数时,都调用那个地方的同一份代码
|
执行速度 |
更快
|
存在函数的调用和返回的额外开销,所以相对慢一些
|
操作符的优先级 |
宏参数的求值是在所有周围表达式的上下文环境里, 除非加上括号,否则邻近操作符的优先级可能会产生 不可预料的后果,所以建议宏在书写的时候多些号。
|
函数参数只在函数调用的时候求 值一次,它的结果值传递给函 数。表达式的求值结果更易预测。
|
带有副作用的参数 |
参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不可预料的结果
|
函数参数只在传参的时候求值一 次,结果更容易控制。
|
参数类型 |
宏的参数与类型无关,只要对参数的操作是合法的, 它就可以使用于任何参数类型。
|
函数的参数是与类型有关的,如 果参数的类型不同,就需要不同 的函数,即使他们执行的任务是 相同的。
|
调试 |
宏是不方便调试的
|
函数是可以逐语句调试的
|
递归 |
宏是不能递归的
|
函数是可以递归的
|
<7>: 命名约定
一般为:
宏的名字全部大写
函数名不全部大写
3. #undef --- 移除宏定义
#include<stdio.h>
#define MAX 100
int main()
{
printf("%d\n", MAX);
#undef MAX
printf("%d\n", MAX);
}
效果为:
去掉 #undef MAX 效果为:
4.命令行定义
当我们根据同一个源文件要编译出一个程序的不同版本的时候,这个特性有点用处。(假定某个
创建数组,不指定大小,在后续补充所需要的大小:
5.条件编译
![](https://img-blog.csdnimg.cn/5fc3cb5e4ee04a029069d38390c89cba.png)
常见的编译指令:
.1>#if 常量表达式//...#endif// 常量表达式由预处理器求值。![]()
.2>多个分支的条件编译#if 常量表达式//...#elif 常量表达式//...#else//...#endif![]()
.3>
判断是否被定义 -- 如果被定义,参与编译,否不参与编译#if defined(symbol)#ifdef symbol -- 定义时,参与#if !defined(symbol)#ifndef symbol --- 没定义时,不参与![]()
.4>
嵌套指令#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
6.文件包含
<1>: 头文件被包含的方式
本地文件的包含:
#include "tese.h" --- 自己写的头文件
查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。 如果找不到就提示编译错误。
上述的标准位置为:
1>Linux环境的标准头文件的路径:/usr/include
2>VS环境的标准头文件的路径:
#include<stdio.h> --- 库里面的头文件
<2>:嵌套文件包含
头文件被重复多次包含:
为避免重复包含,可使用以下操作:
#ifndef __TEST_H__#define __TEST_H__// 头文件的内容#endif
效果为:
#pragma once
效果为:
四:其他预处理指令