做网站套餐,河北爱站网络科技有限公司,最好用的网站开发软件,旅游设计专业网站总言 C语言#xff1a;程序编译相关。 文章目录总言1、程序的翻译环境和运行环境1.1、简述1.2、翻译环境#xff1a;程序编译与链接1.2.1、简介#xff1a;程序如何从.c文件形成.exe可执行程序1.2.2、过程说明1.3、运行环境2、预处理详解2.1、预定义符号2.2、#define2.…总言 C语言程序编译相关。 文章目录总言1、程序的翻译环境和运行环境1.1、简述1.2、翻译环境程序编译与链接1.2.1、简介程序如何从.c文件形成.exe可执行程序1.2.2、过程说明1.3、运行环境2、预处理详解2.1、预定义符号2.2、#define2.2.1、#define定义标识符2.2.2、#define定义宏2.2.3、#define替换规则介绍2.2.4、#和##2.2.4.1、#2.2.4.2、##2.2.5、 带副作用的宏参数2.3、#undef2.4、命令行定义2.5、条件编译2.5.1、格式一2.5.2、格式二多个分支的条件编译2.5.3、判断是否被定义2.5.4、嵌套指令2.6、文件包含2.6.1、头文件包含的方式2.6.1.1、本地文件包含2.6.1.2、库文件包含2.6.2、嵌套包含2.6.2.1、相关现象2.6.2.2、解决方案一2.6.2.3、解决方案二1、程序的翻译环境和运行环境
1.1、简述 在ANSI C的任何一种实现中存在两个不同的环境。 1、翻译环境源代码被转换为可执行的机器指令。 2、执行环境用于实际执行代码。 二者关系如下 1.2、翻译环境程序编译与链接
1.2.1、简介程序如何从.c文件形成.exe可执行程序 1、组成一个程序的每个源文件通过编译过程分别转换成目标代码object code。 2、每个目标文件由链接器linker捆绑在一起形成一个单一而完整的可执行程序。 3、链接器同时也会引入标准C函数库中任何被该程序所用到的函数也可以搜索到个人程序库将其需要的函数链接到程序中。
1.2.2、过程说明 该部分内容后续可完善。
1.3、运行环境 1. 程序要运行则必须载入内存中。在有操作系统的环境中这个步骤一般由操作系统完成。在独立的环境中程序载入一般由手工安排也可能是通过可执行代码置入只读内存来完成。 2. 程序载入内存后就可开始执行其首要调用main函数。这就是之前说的main函数是程序的入口一个工程项目中有且仅有一个main函数。 3、开始执行程序代码。这个时候程序将使用一个运行时堆栈stack存储函数的局部变量和返回地址同时程序也可以使用静态static内存。存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
2、预处理详解
2.1、预定义符号 这些预定义符号都是语言内置的。
__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C其值为1否则未定义演示例子如下
#include Windows.h
int main()
{int i 0;for (i 0; i 10; i){printf(name:%s file:%s line:%d date:%s time:%s i%d\n\n, __func__, __FILE__, __LINE__, __DATE__, __TIME__, i);Sleep(1000);}return 0;
}如果有需要结合文件处理所学可以将其输出为文本文件
#include Windows.h
int main()
{int i 0;FILE* pf fopen(log.txt, a);if (pf NULL){return 1;}for (i 0; i 10; i){printf(name:%s file:%s line:%d date:%s time:%s i%d\n\n, __func__, __FILE__, __LINE__, __DATE__, __TIME__, i);Sleep(1000);}fclose(pf);pf NULL;return 0;
}2.2、#define #define有两个作用一是定义标识符而是定义宏
2.2.1、#define定义标识符 1、引入 我们使用宏定义标识符根据1中所学可知预处理阶段会进行宏替换现在我们来验证它
#define NUM 100200
#define STR Death in the Youngint main()
{int num NUM;char* str STR;return 0;
} 如下图所示左边为我们写的代码右边则是预处理后的内容 关于如何在VS中生成右侧.i文件操作步骤如下 2、假如宏定义标识符中加入了;结果会怎样
#define NUM 100200;
#define STR Death in the Young;以下为我们的验证结果 可以看到会多形成一个语句。在上述情况下这样子的空语句好像不会造成什么影响但在有些场景下使用则会带来麻烦以下为相关举例:
#define NUM 100200;
#define STR Death in the Young;
int main()
{int num 0;if (1)num NUM;elsenum -1;return 0;
} 如图所示为宏替换后的结果可看到if语句后由于多了一条空语句后续的else则没有对应匹配结果。PS此处并非是else就近匹配原则失效。 3、其它例子演示 现在我们可以来简单概括一下#define定义标识符的相关语法
语法#define name stuff需要注意的是name部分是严格按照sutff部分定义如果stuff部分是一个表达式则相应的name部分也会变为表达式。 其它情况演示
#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__ ) 2.2.2、#define定义宏 1、引入 如何用#define定义一个宏函数
#define Add(x,y) xy;int main()
{int result Add(1, 2);printf(%d\n, result);return 0;
}可以看到上述对应宏的地方被替换。 询问上述写法是否具有什么缺陷? 我们在初识C中简单介绍了使用宏需要注意括号问题因此上述代码可以改进如下:
#define Add(x,y) ((x)(y));1、对每个参数都要添加括号 2、对宏整体也要添加括号; 2、宏中参数要添加括号的原因说明 举例一我们的本意是要计算平方可当参数不带括号时若输入的是表达式宏只会原封不动替换以至得到一个错误逻辑
#define SQUARE(x) x*xint main()
{int a 9;int r SQUARE(a1);printf(%d\n, r);return 0;
}预期10*10100,实际结果:91*91193、宏中整体要添加括号的原因说明 举例二宏中函数本意是计算倍数值但因为宏整体不带括号时而输入的又是表达式宏原封不动替换后得到错误结果
#define DOUBLE(x) (x)(x)
int main()
{int ret 3*DOUBLE(100);printf(%d\n, ret);return 0;
}预期:3*(100100)600,实际:3*100100400上述两个例子正确写法如下
#define SQUARE(x) ((x)*(x))
#define DOUBLE(x) ((x)(x))一个结论 用于对数值表达式进行求值的宏定义都应该用这种方式加上括号避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。
2.2.3、#define替换规则介绍 在程序中扩展#define定义符号和宏时需要涉及几个步骤 1. 在调用宏时首先对参数进行检查看看是否包含任何由#define定义的符号。如果是它们首先被替换。 2. 替换文本随后被插入到程序中原来文本的位置。对于宏参数名被他们的值所替换。 3. 最后再次对结果文件进行扫描看看它是否包含任何由#define定义的符号。如果是就重复上述处理过程。 #define DOUBLE(x) (x)(x)
#define Digit 200
int main()
{int ret 3*DOUBLE(Digit);printf(%d\n, ret);return 0;
}int ret 3*DOUBLE(200);//如此类套用下预处理时先解决标识符
int ret 3*(200)(200);//再解决宏注意 1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏不能出现递归。 2. 当预处理器搜索#define定义的符号的时候字符串常量的内容并不被搜索。
#define Digit 200
int main()
{printf(Digit 的取值是 %d\n, Digit);return 0;
}Digit 的取值是 %d\n这是字符串故其中的Digit是不会被替换的。
2.2.4、#和##
2.2.4.1、# 1、铺垫知识字符串是有自动连接的特性 当两个连续字符串中间什么也没有时此处空格可省略不加
int main()
{char* p hello world\n;printf(hello world\n);printf(%s\n,p);return 0;
}2、问题引入
int main()
{int a 10, b 20, c 30;printf(the value of a is %d\n, a);printf(the value of b is %d\n, b);printf(the value of c is %d\n, c);return 0;
}如上若此类语句具有很多条是否有更高效的方法起到套用、批量的处理 可能我们首先能想到的是自定义函数
void print1(int n)
{printf(the value of n is %d\n, n);
}void print2(char c,int n)
{printf(the value of %c is %d\n, c,n);
}int main()
{int a 10, b 20, c 30;print1(a);print1(b);print2(c,c);return 0;
}如上述若我们使用print1的写法 并不能达到效果若使用print2的效果则需要多传递一个参数。 还有么有什么比较方便的解决办法 3、#的使用演示 关于# 的作用把一个宏参数变成对应的字符串。 演示一 在我们使用宏时若直接代入其效果和使用函数print1类似。
#define PRINT(N) printf(the value of N is %d\n,N)
int main()
{int a 10, b 20, c 30;PRINT(a);PRINT(b);PRINT(c);return 0;
}演示二 若加入了#根据其作用可得如下结果
#define PRINT(N) printf(the value of #N is %d\n,N)
int main()
{int a 10, b 20, c 30;PRINT(a);PRINT(b);PRINT(c);return 0;
} 对比.i文件可看到#把一个宏参数变成对应的字符串的含义即#NN再加之之前1)中铺垫的字符串的连续性特点可以达到完整输出一段字符的效果。且和print2相比少使用了一个参数。 演示三 上述是对相同类型的使用方式假如所给数据是不同类型又该如何处理 写法一
#define PRINT(N,format) printf(the value of #N is format\n,N)
int main()
{int a 10, b 20;double c 22.2, d 33.3;PRINT(a, %d);PRINT(b, %d);PRINT(c, %lf);PRINT(d, %lf);return 0;
} 写法二
#define PRINT(N,p) printf(the value of #N is %#p\n,N)
int main()
{int a 10, b 20;double c 22.2, d 33.3;PRINT(a,d);PRINT(b,d);PRINT(c,lf);PRINT(d,lf);return 0;
}2.2.4.2、## 1、##的使用演示 ##可以把位于它两边的符号合成一个符号。它允许宏定义从分离的文本片段创建标识符。 需要注意的是这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。 代码演示如下
#includestdlib.h
#includetime.h#define STU(name, id) name##idint main()
{srand((unsigned int)time(NULL));int student1 rand() % 40 60;int student2 rand() % 40 60;int student3 rand() % 40 60;int student4 rand() % 40 60;int student5 rand() % 40 60;printf(%d\n, STU(student, 1));printf(%d\n, STU(student, 2));printf(%d\n, STU(student, 3));printf(%d\n, STU(student, 4));printf(%d\n, STU(student, 5));return 0;
}2.2.5、 带副作用的宏参数 如下述代码输出结果是什么
#define MAX(x, y) ((x)(y)?(x):(y))int main()
{int a 5;int b 8;int c MAX(a, b);printf(%d\n, c);printf(%d\n, a);printf(%d\n, b);return 0;
} 结果如图所示 原因如下因为MAX在此处非函数而是宏替换后效果如下再加之后置自增的作用故得到上述结果。 int c ((a) (b) ? (a) : (b));2.3、#undef #undef这条指令用于移除一个宏定义。
#undef NAME
//如果现存的一个名字需要被重新定义那么它的旧名字首先要被移除。代码演示如下
#define MAX(x, y) ((x)(y)?(x):(y))int main()
{int a 5;int b 8;int c MAX(a, b);printf(%d\n, c);printf(%d\n, a);printf(%d\n, b);//……
#undef MAXint d MAX(a, b);printf(%d\n, d);return 0;
}2.4、命令行定义 许多C 的编译器提供了一种能力允许在命令行中定义符号。用于启动编译过程。
//linux 环境演示设INST为我们将设置的符号名
gcc -D INST10 programe.c演示如下: 打开vim写入这样一段C程序可看到我们并没有定义数组大小。
[wjVM-4-3-centos T0307]$ ls
test.c
[wjVM-4-3-centos T0307]$ vim test.c
[wjVM-4-3-centos T0307]$ cat -n test.c1 #includestdio.h2 3 int main()4 {5 int arr[sz];6 int i0;7 for (i0;isz;i)8 {9 arr[i]i;10 }11 for(i0;isz;i)12 {13 printf(%d ,arr[i]);14 }15 return 0;16 }
[wjVM-4-3-centos T0307]$现在我们来运行看看
[wjVM-4-3-centos T0307]$ ls
test.c[wjVM-4-3-centos T0307]$ gcc -o test.out test.c
test.c: In function ‘main’:
test.c:5:13: error: ‘sz’ undeclared (first use in this function)int arr[sz];^
test.c:5:13: note: each undeclared identifier is reported only once for each function it appears in
[wjVM-4-3-centos T0307]$ 可看到程序报错报错原因是sz未定义。 此时就可以使用我们的命令行定义来修正需要注意这步操作也是在预处理阶段完成的。
[wjVM-4-3-centos T0307]$ gcc -o test.out test.c -Dsz10
[wjVM-4-3-centos T0307]$ ls
test.c test.out
[wjVM-4-3-centos T0307]$ ./test.out
0 1 2 3 4 5 6 7 8 9 [wjVM-4-3-centos T0307]$ gcc -o test.out test.c -D sz100
[wjVM-4-3-centos T0307]$ ls
test.c test.out
[wjVM-4-3-centos T0307]$ ./test.out
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 2.5、条件编译 在编译一个程序的时候我们如果要将一条语句一组语句编译或者放弃时可以使用条件编译指令。常见的条件编译指令如下
2.5.1、格式一 语法格式如下
#if 常量表达式
//...
#endif常量表达式为真则条件编译内的语句执行。 代码演示
#define N 1002int main(void)
{
#if 1printf(get you the moon.\n);
#endif#if 0printf(in the end.\n);
#endif#if Nprintf(welcome to the internet\n);
#endifreturn 0;
} 2.5.2、格式二多个分支的条件编译 语法格式如下
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif代码演示
#define N 222
int main()
{
#if 1printf(A Long Way.\n);printf(Whispering Still.\n);
#elif 0printf(Goodbye.\n);printf(Wake up.\n);
#elif Nprintf(Another World.\n);
#elseprintf(Over My Head.\n);
#endifreturn 0;
}2.5.3、判断是否被定义 语法格式如下 写法一
#if defined (symbol) //如果定义了symbol,则执行语句
//...
#endif#if !defined (symbol) //如果未定义symbol,则执行语句
//...
#endif写法二第二种写法没有括号
int main()
{
#ifdef symbol //如果定义了symbol,则执行语句
//...
#endif#ifndef symbol //如果未定义symbol,则执行语句
//...
#endifreturn 0;
}需要注意的是这种判断只针对是否被定义不在意被定义的值。 以下为两种写法的代码演示
#define N 0
int main()
{
#if defined (N)printf(normal no more.\n);
#endif#if !defined (N)printf(the sound of silence.\n);
#endifreturn 0;
}#define N 0
int main()
{
#ifdef Nprintf(normal no more.\n);
#endif#ifndef Nprintf(the sound of silence.\n);
#endifreturn 0;
}2.5.4、嵌套指令 效果如下:
#if defined(OS_UNIX)#ifdef OPTION1unix_version_option1();#endif#ifdef OPTION2unix_version_option2();#endif
#elif defined(OS_MSDOS)#ifdef OPTION2msdos_version_option2();#endif
#endif此类定义在库中常见 2.6、文件包含
2.6.1、头文件包含的方式
2.6.1.1、本地文件包含 1、基础说明 相关格式如下本地头文件包含时需要使用引号
#include filename查找策略①编译器先在源文件所在目录下查找。②如果该头文件未找到编译器就像查找库函数头文件一样在标准位置查找头文件。③如果找不到就提示编译错误。 如下图就是本地源文件所在路径若找不到则去对应的库函数所在文件路径下寻找。
#includetest.h
int mian()
{return 0;
}2、Linux下库函数路径 相关路径:
/usr/include2.6.1.2、库文件包含 1、基础说明 相关格式如下对于库函数其头文件可以直接去标准路径下去查找如果找不到就提示编译错误。
#include filename.h问题对于库函数头文件是否可以使用“”的形式包含 回答可以。但从效率角度讲这样子效率相对角度。
2.6.2、嵌套包含
2.6.2.1、相关现象 1、现象演示 #include 包含的头文件会在我们的.源文件中展开。预处理器先删除这条指令并用包含文件的内容替换。那么假设这样一个源文件中包含该头文件N次那就实际也被编译了N次。 如图所示 相关代码 2 int add(int x,int y)3 {4 return xy;5 }
~ 1 #includestdio.h2 3 #includetest.h 4 #includetest.h5 #includetest.h6 int main()7 {8 int a10;9 int b20;10 printf(%d\n,add(a,b));11 return 0;12 }
~835 # 943 /usr/include/stdio.h 3 4
836
837 # 2 test.c 2
838
839 # 1 test.h 1
840
841 int add(int x,int y)
842 {
843 return xy;
844 }
845 # 4 test.c 2
846 # 1 test.h 1
847
848 int add(int x,int y)
849 {
850 return xy;
851 }
852 # 5 test.c 2
853 # 1 test.h 1
854
855 int add(int x,int y)
856 {
857 return xy;
858 }
859 # 6 test.c 2
860 int main()
861 {
862 int a10;
863 int b20;
864 printf(%d\n,add(a,b));
865 return 0;
866 }2.6.2.2、解决方案一 2、如何解决 方法一 相关格式如下
#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif
//这里的__TEST_H__是根据你的头文件来改变的假如叫mylist.h则为__MYLIST_H__实际运用如下 相关代码如下: 1 #ifndef __TEST_H__2 #define __TEST_H__ 3 int add(int x,int y)4 {5 return xy;6 }7 #endif8 835 # 943 /usr/include/stdio.h 3 4
836
837 # 2 test.c 2
838
839 # 1 test.h 1
840
841
842 int add(int x,int y)
843 {
844 return xy;
845 }
846 # 4 test.c 2
847
848
849 int main()
850 {
851 int a10;
852 int b20;
853 printf(%d\n,add(a,b));
854 return 0;
855 } 2.6.2.3、解决方案二 方法二 相关格式如下
#pragma once实际运用如下 相关代码如下: 9 #pragma once 10 int add(int x,int y)11 {12 return xy;13 }