首页 > 其他分享 >【C语言】文件的编译链接和预处理

【C语言】文件的编译链接和预处理

时间:2024-05-25 09:24:52浏览次数:13  
标签:__ 函数 定义 C语言 编译 参数 预处理 define

文件的编译链接和预处理

程序的翻译环境和执行环境

在ANSIC的任何一种实现中,存在俩个不同的环境:
1.翻译环境,在这个环境中源代码被转换为可执行的机器指令(二进制指令)
2.执行环境,用于执行代码

  • 计算机只能执行二进制的指令

翻译环境

在这里插入图片描述

在这里插入图片描述

在程序编译过程:

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

预处理(预编译)过程

1.#include头文件的包含
2.#define定义符号的替换和删除
3.注释的删除

  • 预处理(预编译)过程属于文本操作过程

编译过程

将C语言代码翻译成汇编代码

  • 语法分析
  • 词法分析
  • 语义分析
  • 符号分析

汇编过程

1.将汇编代码翻译成二进制指令(存放目标文件)
2.形成符号表

链接过程

1.合并段表
2.符号表的合并和符号表的重定义

【注意】把gcc移植到windows环境,编译产生的程序想在windows上运行,就得按照windows的可执行程序的格式进行

运行环境

在程序执行的过程:

1.程序必须载入内存中,在有操作系统的环境中:一般由操作系统完成,在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存完成

2.程序的执行便开始。接着便调用main函数

3.开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。

4.终止程序。正常终止main函数;也有可能是意外终止。

预处理详解

预处理符号

在这里插入图片描述

int main(void)
{
	printf("%s\n",__FILE__);
	printf("%s\n",__LINE__);
	printf("%s\n",__DATE__);
	printf("%s\n",__TIME__);
	printf("%s\n",__STDC__);
	printf("%s\n",__FUNCTION__);
	return 0;
}
  • 这些预定义符号都是语言内置

预处理指令

1.#define
2.#include
3.#pragma
4.#error
5.#line

#define

#define定义标识符

#define name staff

  • define 定义一个数字
#define MAX 100
#define min 5
  • define 创建一个新名字
#define reg register
  • define 使用另外一个符号替换实现
#define do_forever for(;;)

注意:一般使用define定义标识符时,不使用;

#define定义宏

#define机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏或者定义宏

  • 宏的声明方式:
    #define name(parament-list) stuff
    其中的parament-list是一个由逗号隔开的符号表,可能出现在stuff中。

【注意】参数列表的左括号必须与name紧邻,如果俩种之间由任何空白存在,参数列表就会被解释为stuff的一部分

  • 宏可以看作是一种替换
#define ADD(x,y) x+y

int main(void)
{
	int a = 10;
	int b = 20;
	printf("%d",ADD(a,b));
	return 0;
}

【注意】所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。

#define替换规则

在程序中扩展#define定义符号和宏时,需要涉及几个步骤。

  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
  2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。
  3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。

注意:

  1. 宏参数和#define 定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归。
  2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

#与##

#的使用

将宏的参数变成对应的字符串

#define PRINTF(format,value)  printf("the value of "#value"is"format"\n",value)

int main(void)
{
	int i = 10;
	PRINT("%d",i);
	return 0;
}

##的使用

可以把位于它俩边的符号合成一个符号,允许宏定义从分离的文本片段创建标识符

#define F(x,y) x##E##y
#include<stdio.h>
int main(void)
{
	printf("%f",F(2,-5));
	return 0;
}

带有副作用的宏参数

#define ADD(x,y) x+y
int main(void)
{
	int b = ++a;
	printf("%d",ADD(b,b));

	return 0;
}

当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。

宏与函数的对比

宏的优势

  1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
  2. 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关的。

函数的优势

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

宏与函数的命名约定

  • 宏的名字全部大写
  • 函数名的每个英文单词首字母大写

undef

#undef用于移除一个宏定义

#define ADD(x,y) x+y
#undef ADD

命令行定义

在命令行中定义符号。用于启动编译过程。

条件编译

在编译一个程序的时候如果一条语句(一组语句)编译或者放弃是很方便的

  • 这种情况适合于:调试型代码,删除可惜,保留多余
#if 常量表达式
 		//... 
#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 

标签:__,函数,定义,C语言,编译,参数,预处理,define
From: https://blog.csdn.net/dab112/article/details/138812481

相关文章

  • aws jsii 基于js 实现跨语言交互的编译器
    jsiiaws开源的,让我们可以基于js实现跨语言交互的编译器,我们可以基于ts开发功能,然后通过编译器jsii可以实现其他语言的通信,目前支持C#,golang,java,pythonruntime参考架构如下图说明从架构上我们可以看出jsii的通信是基于了标准输入输出的处理,实际内部处理后边研究下参考资......
  • C语言 基本算术运算
    函数表达e的x次方:exp(x)x的y次方:pow(x,y)根号x:    sqrt(x)|x|:      abs(x)lnx:      log(x)lgx:     sinx:    sin(x)cosx:    cos(x)分离个位十位百位千位的数字千位:x/1000%10百位:x/100%10十位:x/10%10......
  • 初识C语言——数组详解
    C语言数组相关的详述,值得一看。文章目录一、数组的概念二、一维数组1.一维数组的创建和初始化1.1数组创建1.2数组的初始化2.数组的类型3.一维数组的使用3.1数组的打印3.2数组的输入4.⼀维数组在内存中的存储5.sizeof求数组元素个数三、二维数组1.二维数组的创建和......
  • 【华为OD】D卷真题 100分: 阿里巴巴找黄金宝箱(I) C语言代码实现[思路+代码]
    【华为OD】2024年C、D卷真题集:最新的真题集题库C/C++/Java/python/JavaScript【华为OD】2024年C、D卷真题集:最新的真题集题库C/C++/Java/python/JavaScript-CSDN博客 JS、C、python、C++、Java代码实现:【华为OD】D卷真题100分:阿里巴巴找黄金宝箱(I)JavaScript代码实现......
  • 手把手教你编译属于自己的内核--->WSL-Linux子系统编译安装内核教程
    准备步骤前言:文章操作wsl子系统为ubuntu1.到LINUX内核官网下载最新版的内核Linux内核官网:Linux内核官网点击黄色按钮即可下载最新版本内核解压tarxvJflinux-6.9.1.tar.xz2.使用gitclone到github下载WSL2内核源码到终端输入​sudogitclone https://github.com/......
  • 编写C语言计算器:探索挑战与优化之路
    如果你对C语言编程充满兴趣,那么构建一个简易计算器可能是一个很好的练习机会。在本文中,我们将探讨如何使用C语言实现一个基本的计算器,并分享我们在这个过程中遇到的挑战及其解决方案。版本1.0如下:#define_CRT_SECURE_NO_WARNINGS1#include<stdio.h>voidmenu(){ p......
  • C语言中二维数组和数组名的二意性
    1.二维数组二维数组的本质,也是一维数组,一维数组中的每个元素,又是一个一维数组声明/定义:int[4]array[3]=>intarray[3][4];intmain(){inta[3][4];printf("&arr[0][0]=%p\n",&a[0][0]);//0x16f38b2e8printf("&arr[0]=%p\n",&a[0]);//0......
  • cmakelist 编译源码生成动态静态库并链接到项目
    当我们使用vscode编译c++代码时,需要加入第三方代码,而它没有库时。这时候我们就需要自己写一个Cmakelist编译成库,然后链接到自己的项目上。下面我以qt的qtpropertybrowser类为例,这个类并不在qt的标准库中,若是在qtcreator中使用,需要在pro引入该文件路径(qt安装目录里-\Qt\5......
  • 实验5 C语言指针应用编程
    1.实验任务1task1_1.c1#include<stdio.h>2#defineN534voidinput(intx[],intn);5voidoutput(intx[],intn);6voidfind_min_max(intx[],intn,int*pmin,int*pmax);78intmain()9{10inta[N];11intmin,max;1213......
  • 【c语言】一篇文章搞懂函数递归
    ......