目录
本章主要讲解点:
任何一个代码从源文件(.c文件)到可执行文件(.exe文件)都经历了两个环境
在这个环境中源代码被转换为可执行的机器指令
用于实际执行代码
组成一个程序的每个源文件(.c文件)通过编译过程分别转换成目标代码(.obj文件)
每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序(.exe文件) 链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个 人的程序库,将其需要的函数也链接到程序中
是一个二进制文件,包含了各种源程序可能用到的函数或类等
翻译阶段详解
#include<stdio.h>
#define X 10
#define Y 20
int main()
{
//this is a add ;
int sum = X + Y;
printf("sum = %d\n",sum);
return 0;
}
选项
gcc -E test.c -o test.i
(预编译完成之后就停下来)
注:预编译也就是文本操作,所产生的结果都放在test.i 文件中(临时文件)
选项 gcc -S test.c (编译完成之后就停下来)
注:编译后生成汇编代码,结果保存在test.s中
选项 gcc -c test.c(汇编完成之后就停下来)
注:汇编后生成可重定位的目标文件(由汇编指令转成二进制指令),结果保存在test.o中
符号表
只对全局变量生成符号表
注:每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序(.exe文件)
将每个单独的.o文件形成的符号表合并成为一个符号表 将相同标识符的符号表成员保留有效对应地址
注:声明全局变量的地址是无效地址,只存在声明则会报错存在未定义标识符
可执行文件的运行是在运行环境中运行的
__FILE__ //进行编译的源文件(文件绝对路径)
__LINE__ //当前代码的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义(gcc有定义,vs没有)
//注意是两个下划线
printf("file:%s line:%d\n", __FILE__, __LINE__);
#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__ )
注意:建议在define定义标识符的时候不要在最后加上 ;
#define MAX 1000;
if(condition)
max = MAX;
//效果:max=1000;;(err)
else
max = 0;
允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(defifine macro)
#define name( parament-list ) stuff
注:其中的 parament - list 是一个由逗号隔开的符号表,它们可能出现在 stuff 中
#define SQUARE( x ) x * x
SQUARE( 5 );
//效果:5 * 5
#define SQUARE(x) x*x
#define DOUBLE(x) (x) + (x)
int a = 5;
printf("%d\n" ,SQUARE( a + 1 ) );
printf("%d\n" ,10 * DOUBLE(a));
//效果:printf ("%d\n",5 + 1 * 5 + 1 );
//效果:printf ("%d\n",10 * (5) + (5));
//输出结果:11 55
宏定义只是简单的文本替换,由替换产生的表达式并不会按照预想的次序进行求值
#define SQUARE(x) (x) * (x)
#define DOUBLE(x) ((x) + (x))
注:对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数 中的操作符或邻近操作符之间不可预料的相互作用
宏参数和 #defifine 定义中可以出现其他 #define 定义的变量,但是对于宏,不能出现递归 当预处理器搜索 #defifine 定义的符号的时候,字符串常量的内容并不被搜索
# 把一个宏参数变成对应的字符串
字符串自动连接
char* p = "hello " "world\n";
printf("hello" " world\n");
printf("%s", p);
字符串作为宏参数
#define PRINT(FORMAT, VALUE)\
printf("the input is "FORMAT"\n", VALUE);
#include<stdio.h>
int main()
{
PRINT("%d", 10);
PRINT("%lf", 3.14);
PRINT("%c", 'z');
PRINT("%s", "hello world");
}
#include<stdio.h>
#define PRINT(FORMAT, VALUE)\
printf("the input of " #VALUE " is "FORMAT "\n", VALUE);
int main()
{
int i = 10;
double d = 3.14;
char ch = 'z';
char str[] = "hello world";
PRINT("%d", i );
PRINT("%lf", d );
PRINT("%c", ch );
PRINT("%s", str );
}
注:代码中的#VALUE 会预处理器处理为 "VALUE"
##可以把位于它两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符
#define ADD_TO_SUM(num, value) \
sum##num += value;
//效果为sum5+=10;
int main()
{
int sum5 = 0;
ADD_TO_SUM(5, 10);
printf("%d\n", sum5);
}
注:这样的连接必须产生一个合法的标识符,否则其结果就是未定义的
注:一般很少用,但在阅读源码时很有可能遇到
当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果 副作用就是表达式求值的时候出现的永久性效果
x+1;//不带副作用 x不改变
x++;//带有副作用 x会改变
#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++) > (y++) 比较时x=5>y=8 比较后x=6 y=9
//返回时y=9 返回后y=10
//输出结果:x=6 y=10 z=9
对于带副作用的参数,宏体内可能会求值多次,结果难以预料
宏通常被应用于执行简单的运算
#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));
注:非常便利,简直是奇效~
一般来讲函数的宏的使用语法很相似(语言本身没法区分二者)
宏名全部大写 函数名不要全部大写
用于移除一个宏定义
#undef NAME
//如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除
#include<stdio.h>
#define M 10
int main()
{
printf("%d\n", M);
#undef M
printf("%d\n", M);
return 0;
}
许多C的编译器提供了一种能力,允许在命令行中定义符号,用于启动编译过程
当我们根据同一个源文件要编译出不同的一个程序的不同版本的时候,这个特性有点用处(假定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器内存大写,我们需要一个数组能够大写)
#include <stdio.h>
int main()
{
int array [SZ];
int i = 0;
for(i = 0; i< SZ; i ++)
{
array[i] = i;
}
for(i = 0; i< SZ; i ++)
{
printf("%d " ,array[i]);
}
printf("\n" );
return 0;
}
编译指令: gcc - D SZ = 10 programe . c
注:-D后面有无空格效果都一样
在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃可以使用条件编译
调试性的代码,删除可惜,保留又碍事,可以选择性的编译
#include <stdio.h>
#define __DEBUG__
int main()
{
int i = 0;
int arr[10] = {0};
for(i=0; i<10; i++)
{
arr[i] = i;
#ifdef __DEBUG__
printf("%d\n", arr[i]);//为了观察数组是否赋值成功。
#endif //__DEBUG__
}
return 0; }
#if 常量表达式
//...
#endif
//常量表达式由预处理器求值
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
#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 指令可以使另外一个文件被编译
预处理器先删除这条指令,并用包含文件的内容替换
注:这样一个源文件被包含 10 次,那就实际被编译 10 次
本地文件包含
#include "filename"
先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件,如果找不到就提示编译错误
/usr/include
C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include
注:按照自己的安装路径去找
库文件包含
#include <filename.h>
查找头文件直接去标准路径下去查找,如果找不到就提示编译错误
注:对于库文件也可以使用 “” 的形式包含, 但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了
在项目文件中会出现文件被重复包含的情形
注:这样最终程序中就会出现两份 comm.h 的内容,造成了文件内容的重复
解决方案
#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif //__TEST_H__
图示:
条件编译前
条件编译后
#pragma once
//避免头文件的重复引入