长沙高端网站建设,网站开发与iso9001关系,古田住房与建设局网站,wordpress加入图片不显示程序的翻译环境和执行环境
在ANSI C的任何一种实现中#xff0c;存在两个不同的环境。第1种是翻译环境#xff0c;在这个环境中源代码被转换为可执行的机器指令。第2种是执行环境#xff0c;它用于实际执行代码。
编译和链接
一份源代码(比如test.c)需要通过编译#xf…程序的翻译环境和执行环境
在ANSI C的任何一种实现中存在两个不同的环境。第1种是翻译环境在这个环境中源代码被转换为可执行的机器指令。第2种是执行环境它用于实际执行代码。
编译和链接
一份源代码(比如test.c)需要通过编译形成一份目标文件然后与库连接起来才能形成一份可执行程序test.exe。 编译的过程
编译的过程为预处理预编译、编译、汇编。
预处理在预处理阶段源文件包含的头文件会被展开注释会被去掉宏会进行替换等等。注意此时还不算是运行了程序因为还没形成可执行程序。
编译在编译阶段会把C语言、C语言等等翻译成汇编语言会进行语法分析词法分析符号总汇语义分析。其中的符号总汇是把全局变量函数名称总汇。
汇编把汇编代码转化成二进制指令形成符号表。符号表里面是函数名称和其对应的地址如果该函数没有被定义则会给一个无效地址。
链接
在此阶段会合并段表进行符号表的合并和重定位将所有涉及的库链接起来。符号表的合并的作用是能够找到需要的函数、全局变量等等。 编译源文件的测试我们可以在gcc下进行
1. 预处理 选项 gcc -E test.c -o test.i 预处理完成之后就停下来预处理之后产生的结果都放在test.i文件中。
2. 编译 选项 gcc -S test.c 编译完成之后就停下来结果保存在test.s中。
3. 汇编 gcc -c test.c 汇编完成之后就停下来结果保存在test.o中
程序的运行环境
程序执行的过程
1. 程序必须先载入内存中。在有操作系统的环境中一般这个由操作系统完成。在独立的环境中程序的载入必须由手工安排也可能是通过可执行代码置入只读内存来完成
2. 程序的执行便开始。接着便调用main函数。
3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈stack存储函数的局部变量和返回地址。程序同时也可以使用静态static内存存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
4. 终止程序。正常终止main函数也有可能是意外终止
预处理
预定义符号 __FILE__ __LINE__ __DATE__ __TIME__ __STDC__ //进行编译的源文件 //文件当前的行号 //文件被编译的日期 //文件被编译的时间 //如果编译器遵循ANSI C其值为1否则未定义
这些预定义符号都是语言内置的。
#define
使用#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__ )
注意在define定义标识符的时候不要加上 ; 。
#define定义宏
#define 机制包括了一个规定允许把参数替换到文本中这种实现通常称为宏macro或定义宏define macro。
宏的申明方式#define name( parament-list ) stuff。其中的 parament-list 是一个由逗号隔开的符号表它们可能出现在stuff中。
注意
①参数列表的左括号必须与name紧邻。如果两者之间有任何空白存在参数列表就会被解释为stuff的一部分。
②在定义宏的时候不要吝啬括号。
#includestdio.h
#define SQUARE(x) x*x //51 r 51*51 11
#define SQUARE(x) (x)*(x) // (51)*(51) 6*6 36
#define DOUBLE(x) (x)(x) //k 6 s 33
#define DOUBLE(x) ((x)(x))//k 60int main()
{int r SQUARE(51);//宏是替换不是计算再替换过去printf(%d\n, r);int k DOUBLE(3);//6//这里需要括起来因此最好外面再括号一下int s 10 * DOUBLE(3);//如果不括号 s 10*(3)(3)return 0;
}
#define替换规则
在程序中扩展#define定义符号和宏时需要涉及几个步骤。
①在调用宏时首先对参数进行检查看看是否包含任何由#define定义的符号。如果是它们首先被替换。
②替换文本随后被插入到程序中原来文本的位置。对于宏参数名被他们的值所替换。
③最后再次对结果文件进行扫描看看它是否包含任何由#define定义的符号。如果是就重复上述处理过程
注意
①宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏不能出现递归。
②当预处理器搜索#define定义的符号的时候字符串常量的内容并不被搜索。
#define PRINT(N,X) printf(the value of #N is #X\n,N)
//#N,就是将a或b转换成“a” “b”int main()
{int a 2;PRINT(a,%d);double b 2.5;PRINT(b,%.1lf);return 0;
} ##
##可以把位于它两边的符号合成一个符号。它允许宏定义从分离的文本片段创建标识符
利用##我们可以将参数插入到字符串当中。
#define CAT(Class,num) Class##num
int main()
{int Class106 100;printf(%d\n, CAT(Class, 106));return 0;
} 带副作用的宏参数
当宏参数在宏的定义中出现超过一次的时候如果参数带有副作用那么你在使用这个宏的时候就可能出现危险导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。
//有副作用的宏参数//什么副作用
int main()
{int a 10;int b a 1;int c a;//副作用导致a的值也变了return 0;
}#define MAX(a,b) ((a)(b)?(a):(b))
int main()
{//int m MAX(2, 3);int a 5;int b 4;int m MAX(a, b);// ((a)(b)?(a):(b))//(5,4) ((5,使用后变6)(4使用后变为5)?(6使用后7):(b没使用))printf(%d\n, m);//6printf(%d\n, a);//7printf(%d\n, b);//5return 0;
}
宏和函数对比
宏通常被应用于执行简单的运算。比如在两个数中找出较大的一个
//宏
#define MAX(a,b) ((a)(b)?(a):(b))
//函数
int MAX(int a, int b)
{return (a b ? a : b);
}
其实对于这样简单的任务用宏来进行会比使用函数的效率高。
原因有二
①用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
②更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏可以适用于整形、长整型、浮点型等可以用于来比较的类型。宏是类型无关的。
宏有时候可以做函数做不到的事情。比如宏的参数可以出现类型但是函数做不到。
#define MALLOC(num, type) (type *)malloc(num * sizeof(type))
//使用宏MALLOC灵活地使用不同的类型
MALLOC(10, int);//类型作为参数
MALLOC(10, double);//类型作为参数
//预处理器替换之后
(int*)malloc(10 * sizeof(int));
(double*)malloc(10 * sizeof(double));//原本的malloc的使用,需要分开写
(int*)malloc(10 * sizeof(int));
(double*)malloc(10 * sizeof(double));
宏相对函数的缺点
① 每次使用宏的时候一份宏定义的代码将插入到程序中。除非宏比较短否则可能大幅度增加程序的长度。
②宏是没法调试的。
③宏由于类型无关也就不够严谨。这一点是宏的一把双刀刃即使优点也是缺点。
④宏可能会带来运算符优先级的问题导致程容易出现错。因此不能吝啬括号。
总结宏和函数的对比
属 性#define定义宏函数代 码 长 度每次使用时宏代码都会被插入到程序中。除了非常小的宏之外程序的长度会大幅度增长函数代码只出现于一个地方每 次使用这个函数时都调用那个 地方的同一份代码执 行 速 度更快存在函数的调用和返回的额外开 销所以相对慢一些操 作 符 优 先 级宏参数的求值是在所有周围表达式的上下文环境里除非加上括号否则邻近操作符的优先级可能会产生不可预料的后果所以建议宏在书写的时候多些括号。函数参数只在函数调用的时候求 值一次它的结果值传递给函 数。表达式的求值结果更容易预 测。带 有 副 作 用 的 参 数参数可能被替换到宏体中的多个位置所以带有副作用的参数求值可能会产生不可预料的结果。函数参数只在传参的时候求值一 次结果更容易控制。参 数 类 型宏的参数与类型无关只要对参数的操作是合法的它就可以使用于任何参数类型。函数的参数是与类型有关的如 果参数的类型不同就需要不同 的函数即使他们执行的任务是 不同的。调 试宏是不方便调试的函数是可以逐语句调试的递 归宏是不能递归的函数是可以递归的
宏命名的约定
一般来说一般都是英文全大写来命名宏。不过也有会采用小写我们需要懂得分辨。
#undef
这条指令用于移除一个宏定义.
#define MAX 100
int main()
{printf(%d\n, MAX);
#undef MAXprintf(%d\n, MAX);//MAX被移除了这里报错return 0;
}
条件编译
在编译一个程序的时候我们如果要将一条语句一组语句编译或者放弃是很方便的。因为我们有条件编译指令。
#include stdio.h
#define __DEBUG__ //当把这条宏定义注释掉那么就不会执行printf
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
//常量表达式由预处理器求值。
//如
#define __DEBUG__ //1
#if __DEBUG__
//..
#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
文件包含 #include 指令可以使另外一个文件被编译。就像它实际出现于 #include 指令的地方一样。这种替换的方式很简单那就是预处理器先删除这条指令并用包含文件的内容替换。这样一个源文件被包含10次那就实际被编译10次。很显然这样是很不好的如果不小心包含了多个同样的头文件每个头文件里面有几千行代码那么重复的代码就会非常的多。
因此我们可以在头文件中加入条件编译
#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif //__TEST_H__ 或者是#pragma once 这样一来就不会编译多次同样的源文件了。
对于头文件来说有以下两种形式
①#include ...... :以来包含头文件名的直接去标准路径下查找头文件、
②#include ...... 以来包含头文件名的先是去源文件的路径下寻找找不到再去标准路径中找。这种效率比较低。