在ANSI C的任何一种实现中,存在两种不同的环境。第一种是翻译环境,在这个环境中,源代码被转换为可执行的机器指令。第二种是执行环境,它用于实际执行代码。标准明确说明,这两种环境不必位于同一台机器上。例如,交叉编译器就是在一台机器上运行,但它所产生的可执行代码运行于不同类型的机器上。

程序的翻译和执行

翻译


组成一个程序的每个源文件通过编译过程分别转换成目标代码
每个目标文件由链接器捆绑在一起,形成一个单一而完整的可执行程序
链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且他可以搜索程序猿客人的程序库,将需要的函数链接到程序中

执行

程序执行过程

  • 首先,程序必须载入到内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
  • 然后,程序的执行便开始。接着便执行main函数
  • 然后,开始执行程序代码。这个时候程序将使用一个运行时堆栈,存储函数的局部变量和返回地址。程序同时也可以使用静态内存,存储于静态内存中的变量在程序的整个执行过程一直保留它们的值。
  • 最后,终止程序,正常终止main函数,也有可能是意外终止

程序的翻译流程

预处理

命令:gcc -E ×××.c -o ×××.i
主要工作
拷贝头文件到×××.i。
去掉注释
进行展开
处理条件编译

代码演示

#include <stdio.h>#define N 5/*** 测试用例 ***/int main(){#if 1printf("hello, world! month: %d\n", N);
#elseprintf("hehe, world! month: %d\n", N);
#endifreturn 0;
}

预处理之后

835 # 943 "/usr/include/stdio.h" 3 4
836
837 # 2 "pretreatment.c" 2
838
839
840
841
842
843 int main(){844
845
846  printf("hello, world! month: %d\n", 5);
847
848
849
850
851  return 0;
852 }

前面800多行都是stdio.h头文件的展开。可以看到,预处理之后,注释被去掉了,宏也进行了展开,条件编译也被处理。

预定义符号

__FILE__ 进行编译的源文件
__LINE__ 文件当前的行号
__DATE__ 文件被编译的日期
__TIME__ 文件被编译的时间
__STDC__ 如果编译器遵循ANSI C,其值为1,否则未定义

这些预定义符号都是语言内置的。

代码演示

#include <stdio.h>int main(){printf("files: %s\tline: %d\n", __FILE__, __LINE__);printf("date: %s\t\ttime: %s\n", __DATE__, __TIME__);printf("stdc: %d\n", __STDC__);return 0;
}
运行结果
[sss@aliyun order]$ !gcc
gcc pre_define_symbol.c -o pre_define_symbol
[sss@aliyun order]$ ./pre_define_symbol
files: pre_define_symbol.c  line: 5
date: May  5 2019       time: 19:59:18
stdc: 1

语法

#define constant 100
#define reg         register
#define do_forever  for(;;)
#define CASE        break;case
#define DEBUG_PRINT printf("File: %s. Line: %d."\"x = %d, y = %d, z = %d",\__FILE__, __LINE__,\x, y, z)

宏常量

语法

#define name stuff
代码示例
#include <stdio.h>#define N 10int main(){printf("hello, world! %d\n", N);return 0;
}
运行结果
[sss@aliyun order]$ gcc macro_constant.c -o macro_constant
[sss@aliyun order]$ ./macro_constant
hello, world! 10

宏函数

语法:

#define name(parament_list) stuff
其中parament_list是一个由逗号隔开的符号表,它们可能出现在stuff中。

注意:参数列表的左括号必须与name紧邻。如果两者存在任何空白,参数列表就会被解释为stuff的一部分。

代码演示
#include <stdio.h>#define MAX(a, b) a > b ? a : bint main(){int a = 1;int b = 2;printf("max: %d\n", MAX(a, b));return 0;
}
运行结果
[sss@aliyun order]$ gcc macro_function.c -o macro_function
[sss@aliyun order]$ ./macro_function
max: 2
宏函数的一个注意事项
代码演示
#include <stdio.h>#define SQUARE(X) X * Xint main(){int num = 3;printf("square(num + 1): %d\n", SQUARE(num + 1));return 0;
}
运行结果
[sss@aliyun order]$ !gcc
gcc macro_function.c -o macro_function
[sss@aliyun order]$ ./macro_function
square(num + 1): 7

可见宏函数只是简单的文本替换

SQUARE(num + 1)
num + 1 * num + 1

#undef

用于移除一个宏
语法

#define N 10
#undef N

#和##

首先来看一段代码

#include <stdio.h>int main(){char* p = "hello, ""world!\n";printf("hello, ""world!\n");printf("%s", p);return 0;
}
运行结果
[sss@aliyun order]$ gcc test.c -o test
[sss@aliyun order]$ ./test
hello, world!
hello, world!

从这里,我们可以看出字符串是有自动连接的特点的。

#用法示例

#include <stdio.h>#define PRINT(FORMAT, VALUE)\printf("the value is "FORMAT"\n", VALUE);/*** #的作用,把一个宏参数编程对应的字符串 ***/
#define PRINT2(FORMAT, VALUE)\printf("the value of "#VALUE" is "FORMAT"\n", VALUE);int main(){int i = 10;PRINT("%d", 10);PRINT2("%d", i + 3);return 0;
}
运行结果
[sss@aliyun order]$ !gcc
gcc test.c -o test
[sss@aliyun order]$ ./test
the value is 10
the value of i + 3 is 13

##用法示例

#include <stdio.h>#define PRINT(FORMAT, VALUE)\printf("the value is "FORMAT"\n", VALUE);/*** #的作用,把一个宏参数编程对应的字符串 ***/
#define PRINT2(FORMAT, VALUE)\printf("the value of "#VALUE" is "FORMAT"\n", VALUE);/*  * #的作用,把位于它两边的符号合成一个符号,* 它允许宏定义从分离的文本片段创建标识符 */
#define ADD_TO_SUM(num, value) \sum##num += value;int main(){int i = 10;int sum5 = 0;PRINT("%d", 10);PRINT2("%d", i + 3);ADD_TO_SUM(5, 10);printf("sum5: %d\n", sum5);return 0;
}
运行结果
[sss@aliyun order]$ gcc macro_20190505.c -o macro
[sss@aliyun order]$ ./macro
the value is 10
the value of i + 3 is 13
sum5: 10

宏和函数

属性 函数
代码长度 每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅度增长。 函数代码只出现在一个地方;每次使用这个函数时,都调用同一份代码。
执行速度 更快。 存在函数的调用和返回的额外开销,相对慢一些。
操作符优先级 宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则临近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写时多些括号。 函数参数只在函数调用的时候求值一次,它的结果传递给函数。表达式的求值结果更容易预测。
带有副作用的参数 参数可能被替换到宏体中的多个位置,所以带有副作用的参数求值可能会产生不可预料的结果。 函数参数只在传参的时候求值一次,结果更容易控制。
参数类型 宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用任何类型参数。 函数的参数是与类型有关的,如果函数的类型不同,就需要不同的函数,即使他们执行的任务是相同的。
调试 宏是不方便调试的。 函数是可以逐语句调试的。
递归 宏是不能递归的。 函数是可以递归的。

命令行定义

许多C编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。例如:当我们根据同一个源文件要编译出一个程序的不同版本时,这个特性有点用处。(假定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器内存很大,我们需要一个大些的数组。)

代码演示

#include <stdio.h>int main(){int arr[LEN];int i = 0;for(; i < LEN; ++i){arr[i] = i;}printf("The array is: \n");for(i = 0; i < LEN; ++i){printf("%d ", arr[i]);}printf("\n");return 0;
}
运行结果
[sss@aliyun order]$ gcc -DLEN=10 command_line_define.c -o command_line_define
[sss@aliyun order]$ ./command_line_define
The array is:
0 1 2 3 4 5 6 7 8 9
[sss@aliyun order]$ gcc -DLEN=5 command_line_define.c -o command_line_define
[sss@aliyun order]$ ./command_line_define
The array is:
0 1 2 3 4

条件编译

在编译一个程序的时候,我们如果要将一条语句编译或者放弃是很方便的。我们可以用条件编译指令。

代码演示

#include <stdio.h>
#define __DEBUG__int main(){int i = 0;int arr[10] = {0};for(; i < 10; ++i){arr[i] = i;#ifdef __DEBUG__printf("%d\n", arr[i]);#endif}return 0;
}

常见的条件编译指令

#if 常量表达式// ...
#endif/*** 多分支条件编译 ***/
#if 常量表达式// ...
#elif 常量表达式// ...
#else// ...
#endif/*** 判断symbol是否被定义 ***/
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol/*** 嵌套指令 ***/
#if defined(OS_UNIX)#ifdef OPTION1unix_version_option1();#endif#ifdef OPTION2unix_version_option2();#endif
#elif defined(OS_MSDOS)#ifdef OPTION2msdos_version_option2();#endif
#endif

文件包含

我们已经看到过,#include指令使另一个文件的内容被编译,就像它实际出现于#include指令的地方一样。这种替换的方式很简单:预处理器先删除这条指令,并用包含文件的内容替换。这样一个源文件被包含10次,那就实际被编译10次。

头文件包含方式

本地头文件包含
#include "×××.h"

该种包含方式,先在源文件所在目录下查找,找不到的话就到标准库文件所在路径进行查找

库文件包含
#include <×××.h>

该种包含方式,直接去标准库文件所在路径进行查找,找不到报错。
Linux环境的标准头文件路径:

/usr/include

VS环境的标准头文件的路径:

C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC/include

嵌套文件包含


comm.h和comm.c是公共模块。test1.h和test1.c使用了公共模块。test2.h和test2.c使用了公共模块。test.h和test.c使用了test1模块和test2模块。这样最终程序中就会出现两份comm.h的内容。这样就造成了文件内容的重复包含。
如何解决这个问题
条件编译

#ifndef __TEST_H__
#define __TEST_H__
// 头文件内容
#endif#pragma once

上面两种方法都可以避免头文件的重复包含

其他预处理指令

#error
#pragma
#line
...

编译

命令:gcc -S ×××.i -o ×××.s
主要工作
预处理过的源代码文件转换为汇编代码

演示

[sss@aliyun order]$ gcc -S hehe.i -o hehe.s
[sss@aliyun order]$ vim hehe.s
 .file   "pretreatment.c".section  .rodata
.LC0:.string    "hello, world! month: %d\n".text.globl    main.type   main, @function
main:
.LFB0:.cfi_startprocpushq   %rbp.cfi_def_cfa_offset 16.cfi_offset 6, -16movq    %rsp, %rbp.cfi_def_cfa_register 6movl   $5, %esimovl    $.LC0, %edimovl $0, %eaxcall    printfmovl  $0, %eaxpopq    %rbp.cfi_def_cfa 7, 8ret.cfi_endproc
.LFE0:.size main, .-main.ident  "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-36)".section    .note.GNU-stack,"",@progbits

汇编

命令:gcc ×××.s -o ×××.o
主要工作:将汇编代码变成二进制指令
注意,×××.o还不能执行。

chmod +x ×××.o

加了权限也不能执行
和可执行程序还差了一步链接

演示

[sss@aliyun order]$ gcc -c hehe.s -o hehe.o
[sss@aliyun order]$ objdump -d hehe.ohehe.o:     file format elf64-x86-64Disassembly of section .text:0000000000000000 <main>:0: 55                      push   %rbp1:   48 89 e5                mov    %rsp,%rbp4:  be 05 00 00 00          mov    $0x5,%esi9:  bf 00 00 00 00          mov    $0x0,%edie:  b8 00 00 00 00          mov    $0x0,%eax13: e8 00 00 00 00          callq  18 <main+0x18>18: b8 00 00 00 00          mov    $0x0,%eax1d: 5d                      pop    %rbp1e:  c3                      retq
[sss@aliyun order]$ chmod +x hehe.o
[sss@aliyun order]$ ll
total 124
-rw-rw-r-- 1 sss sss   184 May  5 19:44 hehe.c
-rw-rw-r-- 1 sss sss 16918 May  5 19:55 hehe.i
-rwxrwxr-x 1 sss sss  1528 May  6 09:21 hehe.o
-rw-rw-r-- 1 sss sss   501 May  6 09:15 hehe.s
[sss@aliyun order]$ ./hehe.o
-bash: ./hehe.o: cannot execute binary file

链接

把二进制指令转换成最终的二进制代码。
多个源代码生成的目标文件×××.o链接才能得到最终的可执行程序
如:printf的定义在printf.c文件中,printf.c经过编译器得到目标文件需要加上C语言标准库提供的函数定义。

『C』程序的翻译执行相关推荐

  1. 『 不老 』程序员之修炼指南

    题图来源:www.pinterest.com 在程序员聚集的技术社区里面,你应该会经常看到下列帖子: 中年程序员的心酸 大龄大专程序员的出路在哪里? 程序员 30 岁之后何去何从? 50 岁以上的程序 ...

  2. 『教师节』程序猿用文心大模型带你一键加速祝福,祝老师们节日快乐

    『教师节』文心大模型带你一键加速祝福 文心大模型助力教师节 猜猜他谁 教师节&&中秋节 中秋助力 大模型使用指南 教师节助力 安装wenxin_api第三方库 定义我们的祝福主题与内容 ...

  3. 『ssh』使用shell远程执行命令

    经常需要远程到其他节点上执行一些shell命令,如果分别ssh到每台主机上再去执行很麻烦,因此能有个集中管理的方式就好了.所以介绍几种shell命令远程执行的方法. 前提条件:配置shell免密登录 ...

  4. 『操作系统』 进程的描述与控制 Part 1 前驱图与程序执行

    文章目录 2.1 前趋图和程序执行 2.1.1 程序的顺序执行及其特征 1. 程序的顺序执行 2.程序顺序执行时的特征 2.1.2 前趋图 2.1.3 程序的并发执行及其特征 1. 程序的并发执行 2 ...

  5. 交大『云观CD-ROMIII』直接由CD-ROM执行之安装法(转)

    交大『云观CD-ROMIII』直接由CD-ROM执行之安装法(转) 笔者很高兴看到交大一群朋友们的努力, 出版了一份本土的 "Plug & Play" Linux CD-R ...

  6. 程序环境——翻译环境与执行环境

    本篇重点: 程序的翻译环境和执行环境 详解翻译环境中的 编译+链接 程序的翻译环境和执行环境 在ANSI的任何一种实现中,存在两个不同的环境. 翻译环境,在这个环境中源代码被转换为可执行的机器指令. ...

  7. [日推荐]『TheGolfGame』一个高端大气上档次的小程序

    今天小编再来给大家推荐一个高端大气上档次的小程序 TheGolfGame 简介:The Golf Game是基于高尔夫教学的服务平台,为用户提供最专业的高尔夫教学指导,大量的教学视频,更有娱乐视频,让 ...

  8. 简单介绍程序的翻译环境和执行环境

    前言:我们常常在程序中看到编译,链接,执行,但是你真的了解过它们吗?作为一名未来的合格的程序员,应当能够对它们有清晰的认知.本文就将对它们进行浓缩精炼的介绍,让你面对它们的时候不再感到迷惑. 想要深入 ...

  9. 『中秋赏月』程序员用文心大模型带你玩转不一样的中秋

    『中秋赏月』文心大模型带你玩转不一样的中秋 中秋 文心大模型使用 安装wenxin_api 利用ERNIE-ViLG文生图模型生成图片 下载生成的图片 使用ERNIE 3.0模型,生成有关中秋的佳句 ...

最新文章

  1. python3运行报错:TypeError: Object of type ‘type‘ is not JSON serializable解决方法(详细)
  2. 深入浅出面向对象和原型【番外篇——重新认识new】
  3. 【效率】神器工具:新一代多系统启动 U 盘装机解决方案
  4. Linux 、shell 时间函数 - 获取七天前所在周
  5. Leetcode PHP题解--D56 637. Average of Levels in Binary Tree
  6. 放弃第三方?苹果正自研iPhone调制解调器芯片
  7. Linux系统实现ICMP ping功能,并计算时延
  8. 手动升级 Confluence - 规划你的升级
  9. Eclipse开发环境设置(Maven+Spring MVC+Flex)
  10. 面试题:老师生日分析过程,能否建模用程序解答?
  11. android 控件覆盖关系,Android设置viewGroup和其子控件两者之间的焦点关系【原创】...
  12. 计算方法实验(二):龙贝格积分法
  13. Shell判断字符串是否为空
  14. 如何自制自平衡云台基于mpu6050,arduino输出三维倾斜角度的方法(含源码,库)
  15. winform打印html文件,c# 如何实现web打印插件
  16. 计算机网络实验如何设置无线路由器密码,怎么设置路由器密码 路由器设置密码方法【图文】...
  17. 逆波兰式求值 —Java
  18. 1w存银行一年多少利息_在银行存定期一万块一年有多少利息?
  19. 替换Android手机的开机动画,安卓技术宅系列之修改手机开机动画
  20. Win10配置pytorch深度学习环境

热门文章

  1. OPJ1002 方便记忆的电话号码
  2. 运算符优先级,对象深拷贝
  3. 结合DVWA的反射型XSS浅析
  4. VSCode语法高亮 禁用括号花俏的颜色
  5. 微机原理与接口技术:数模转换和模数转换 详细笔记
  6. vivado导入tcl例程
  7. 领英工具-领英精灵的批量加好友功能你真的会用吗?
  8. 【Python脚本进阶】2.1、端口扫描器(下):NMAP端口扫描
  9. 电脑摄像头阅卷软件在教学各环节中的实践应用
  10. 2020--管理类联考--网课推荐