目录
一、程序的翻译环境和执行环境
在ANSI C的任何一种实现中,存在两个不同的环境
第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令
第2种是执行环境,它用于实际执行代码
二、详解编译+链接
2.1翻译环境
2.2编译本身的几个阶段
代码如下:
sum.c
int g_val = 2024;
void print(const char* str)
{
printf("%s\n", str);
}
test.c
int main()
{
extern void print(char* str);
extern int g_val;
printf("%d\n", g_val);
print("hello world");
return 0;
}
2.3运行环境
三、预处理详解
3.1预定义符号
![](https://img-blog.csdnimg.cn/direct/3f18b58d07194fb29b827e4c0779514c.png)
![](https://img-blog.csdnimg.cn/direct/9a0a543741b9440d82c878477449d745.png)
3.2#define
3.2.1#define 定义标识符
语法:
#define name stuff
举个例子:
#define MAX 1000
#define reg register //为 register这个关键字,创建一个简短的名字
#define do_forever for(;;) //用更形象的符号来替换一种实现
#define CASE break;case //在写case语句的时候自动把 break写上。
// 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
date:%s\ttime:%s\n" ,\
__FILE__,__LINE__ ,\
__DATE__,__TIME__ )
尽量不要在最后加上; 会在替换中出现语法错误
3.2.2#define 定义宏
#define机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)
下面是宏的申明方式:
#define name( parament-list ) stuff
#define SQUARE(x) x*x
如果宏这样写就会输出
int main()
{
int a = 5;
printf("%d\n",SQUARE(a+1));
}
宏会被替换为
int main()
{
int a = 5;
printf("%d\n",a+1*a+1);
}
这样不会得到我们想要得结果25 所以要加上括号
#define SQUARE(x) ((x)*(x))
3.2.3#define替换规则
3.2.4#和##
如何把参数插入到字符串当中:
利用#可以把一个宏参数变成对应的字符串
示例:
#define PRINT(n, format) printf("the value of "#n" is " format "\n", n)
int main()
{
int a = 20;
//printf("the value of a is %d\n", a);
PRINT(a, "%d");
int b = 15;
//printf("the value of b is %d\n", b);
PRINT(b, "%d");
float f = 4.5f;
//printf("the value of f is %f\n", f);
PRINT(f, "%f");
return 0;
}
##的作用
##可以把位于它两边的符号合成一个符号
它允许宏定义从分离的文本片段创建标识符。
示例:
#define CAT(x,y) x##y
int main()
{
int Class110 = 2024;
printf("%d\n", CAT(Class, 110));
printf("%d\n", Class110);
return 0;
}
3.2.5带副作用的宏参数
#define MAX(x, y) ((x)>(y)?(x):(y))
int main()
{
int a = 5;
int b = 6;
int c = MAX(a++, b++);
printf("a = %d\n", a);
printf("b = %d\n", b);
printf("c = %d\n", c);
//a b c
//
return 0;
}
这里预处理之后为
z = ( (x++) > (y++) ? (x++) : (y++));
所以是会产生影响的
3.2.6宏和函数的对比
#define MAX(a, b) ((a)>(b)?(a):(b))
为什么用宏而不用函数呢
#define MALLOC(num, type)\
(type*)malloc(num * sizeof(type))
MALLOC(10,int);
//预处理过后
(int*)malloc(10*sizeof(int));
宏和函数的对比:
属性 | #define定义宏 | 函数 |
---|---|---|
代码长度 | 每次使用时,宏代码都会替换到程序中除了小一点的宏,程序长度会大幅增长 | 函数代码只出现于一个地方,每次调佣都是那个地方的同一份代码 |
执行速度 | 更快 | 存在函数调用和返回,所以慢一点 |
操作符优先级 | 须加上括号控制后果 | 函数在传值时就会求一次值,更容易预测 |
带有副作用的参数 | 宏参数被替换时有副作用的参数将产生后果 | 函数在传值时就会求一次值,更容易控制 |
参数类型 | 宏的参数与类型无关,只要参数操作合法,它就可以使用于任何参数类型 | 函数的参数与类型有关的,如果参数的类型不同,就需要不同的函数即使他们执行的任务是不同的 |
调试 | 不能 | 能 |
递归 | 不能 | 能 |
命名的约定:
宏名全部大写
函数名不要全部大写
3.3#undef
用于移除一个宏定义
#undef NAME
现有名字若需重新定义,旧名字首先被移除
3.4命令行定义
3.5条件编译
直接上代码
#define M 0
int main()
{
#if M==1
printf("hehe\n");//这条语句在这就不会编译
#endif
return 0;
}
还可以进行多分支
#if 常量表达式
//...
#elif
//...
#else
//...
#endif
判断是否被定义
#if defined(symbol)//如果定义执行
#ifdef symbol
#if !define(symbol)//如果没定义执行
#ifndef symbol
也可以进行嵌套使用,这里就别再展示
3.6文件包含的方式
3.6.1头文件被包含的方式
本地文件包含
#include "filename";
查找规则:现在源文件所在目录下查找,如果头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。找不到就提示编译错误
linux环境的标准头文件的路径:
/usr/include
vs环境的标准头文件的路径:
c:\program files (x86)\Microsoft Visual Studio 12.0\VC\include
按照自己的安装路径查找。
库文件包含
#include <filename.h>
查找规则:直接去标准路径下查找,如果找不到就提示编译错误
虽然包含库文件也可以用" "但是这样查找效率就会变低当然也不容易区分是库文件还是本地文件了
3.6.2嵌套文件包含
如果几个文件嵌套包含之后会出现两份被包含的内容,这样会使文件包含重复如下图
test.c中就会出现两份come.h
如何解决这个问题呢?
条件编译。
每个文件的开头写:
#ifndef __TEST_H__
#define __TEST_H__
//头文件内容
#endif
或者
#pragma onec
就可以避免头文件的重复引入。