【C语言】进阶——程序编译

news2024/10/7 6:28:42

 

目录

一:🔒程序环境

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

💡1.1翻译环境  

预编译阶段:

编译阶段:

汇编阶段:

链接阶段:

💡1.2运行环境 

 二:🔒预处理详解

💡2.1预处理符号

💡2.2#define 

#define定义标识符

#define定义宏

#define替换规则 

 🔒#和## 

💡#的作用

💡##的作用

带副作用的宏参数 

三:宏与函数的对比 

💡命名约定 

 四:🔒条件编译

五:🔒文件包含

💡头文件的包含方式 

💡避免头文件被重复引用 


 

 

一:🔒程序环境

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

在ANSIC任何一种实现中,存在两种不同的环境

        1.翻译环境,在这个环境下源代码被转换成可执行的机器指令

        2.执行环境,用于实际代码运行

 

💡1.1翻译环境  

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

 

预编译阶段:

1.头文件包含

#include 预处理指令

2.define定义的符号替换

#define 预处理指令

3.注释删除

以上这些都是文本操作

编译阶段:

把c语言代码翻译成了汇编代码

1、语法分析

2、词法分析

3、语义分析

4、符号汇总 

汇编阶段:

把汇编指令翻译成了二进制的指令

形成符号表,这样就能够找到源文件外部的符号(只能汇总全局符号)

链接阶段:

1、合并段表

2、符号表的合并和重定位

💡1.2运行环境 

程序执行的过程:

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

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

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

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

 二:🔒预处理详解

💡2.1预处理符号

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

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

int main()
{
	printf("file:%s line:%d\n", __FILE__, __LINE__);
	return 0;
}

💡2.2#define 

#define定义标识符

#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 MAX 100;
int main()
{
    int max = 0;
    if (1)
        max = MAX;    //error
    else
        max = 0;
    return 0;
}

这里宏定义会直接替换,将 100; 替换到 MAX位置

 if (1)
        max = 100;
        ;
    else
        max = 0;

#define定义宏

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

#define name(parament-list) stuff
//name表示名字
//parament-list 是以逗号隔开的参数
//宏的具体内容
 
例子
//#define max(a,b) a+b

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

同样也要注意因为宏是直接进行文本替换,然后才在程序中发生计算,所以如果不按照标准规定写宏,可能会产生bug 

#define SQUARE(x) x*x
 
int c=5SQUARE(5+1);
    //我们预期这里的内容是36,但是最终结果是11,是因为实际计算的是
    //5+1*5+1==11

所以在写的时候我们应该尽可能带上括号,防止因为优先级的问题出现bug

#define SQUARE(x) ((x)*(x))

#define替换规则 

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

1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先 被替换。

2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。

3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上 述处理过程。.

注意:

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

 🔒#和## 

💡#的作用

 如何把参数插入到字符串中?

int main()
{
	printf("who say!!!\n");
	printf("who" " say!!!\n");
	return 0;
}

 

字符串是具备自动连接的特点。

 而define定义的符号,在"X" 里面不会被识别,我们可以用 #X 解决此问题

#define PRINT(n, format)   printf("the value of "#n" is "format"\n", n)
int main()
{
	int a = 10;
	PRINT(a, "%d");
	//printf("the value of ""a"" is ""%d""\n", a);
	//printf("the value of a is %d\n", a);
	int b = 20;
	PRINT(b, "%d");
	return 0;
}

💡##的作用

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

#define CAT(v, n) v##n
int main()
{
	int value10 = 100;
	printf("%d\n", CAT(value, 10));
	printf("%d\n", value10);
	return 0;
}

注:
这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。 

带副作用的宏参数 

简单来讲就是宏在执行的过程中,参数自身的值会发生变化,这个就叫做带副作用的宏的参数

#define MAX(a,b) ((a)>(b)?(a):(b))
 
int main()
{
    int x=5,y=8;
    int c=MAX(x++,y++);
    printf("%d ",c);
    return 0;
}
//这里输出的是多少,9嘛?
//首先带入  ((5++)>(8++)?(a++):(b++)) 
    首先是进行ab大小的比较,在这里比较之后,
//a跟b跟别变成了6和9,然后执行后面的b++,最终的结果应该a=6 c=9 b=10

要避免写出这样的代码,宏是无法调试的

三:宏与函数的对比 

 宏通常被应用于执行简单的运算。
比如在两个数中找出较大的一个。

宏定义和函数的比较 

属性#define宏定义函数
代码长度每次使用时,宏代码都会被插入到程序中。除了非常 小的宏之外,程序的长度会大幅度增长函数代码只出现于一个地方;每 次使用这个函数时,都调用那个、地方的同一份代码
执行速度  相对更快(简单的程序)存在函数的调用和返回的额外开 销,所以相对慢一些

操作符

优先级

宏参数的求值是在所有周围表达式的上下文环境里, 除非加上括号,否则邻近操作符的优先级可能会产生 不可预料的后果,所以建议宏在书写的时候多些括 号。函数参数只在函数调用的时候求 值一次,它的结果值传递给函 数。表达式的求值结果更容易预 测。
带 有 副 作 用 的 参 数惨数可能被替换到宏体中的多个位置,所以带有副作 用的参数求值可能会产生不可预料的结果。函数参数只在传参的时候求值一 次,结果更容易控制。
参数类型 宏的参数与类型无关,只要对参数的操作是合法的, 它就可以使用于任何参数类型。函数的参数是与类型有关的,如 果参数的类型不同,就需要不同 的函数,即使他们执行的任务是 不同的。
调试无法调试函数可以逐语句调试的
递归无法递归可以递归

1.用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。
所以宏比函数在程序的规模和速度方面更胜一筹。(相对简单定义)
2.更为重要的是函数的参数必须声明为特定的类型。
所以函数只能在类型合适的表达式上使用。

反之这个宏怎可以适用于整形、长整型、浮点型等可以
用于来比较的类型。
宏是类型无关的。

💡命名约定 

 一般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者。
那我们平时的一个习惯是:

把宏名全部大写
函数名不要全部大写(一般单词首字母大写)

 四:🔒条件编译

条件编译顾名思义就是满足条件才进行读取

1.
#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次。

💡头文件的包含方式 

本地文件

#include"test.h"

先从源文件所在目录进行查找头文件,然后再到标准函数库头文件所在目录下查找

#include<stdio.h>

直接从标准函数库头文件所在目录下查找

总的来说就是""的引用方式查找范围更广

但是!!!

本地文件还是按照 #include"test.h''  的方式

库文件按照 #include<test.h>的方式  

否则:查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。

💡避免头文件被重复引用 

防止多次头文件频繁引用

#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif   //__TEST_H__

或者

#pragma once

以上便是我对【C语言】程序编译的介绍,文中不足之处,还望得到指点得以改善。感谢!!! 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1118131.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

进阶JAVA篇-深入了解 Set 系列集合

目录 1.0 Set 类的说明 1.1 Set 类的特点 1.2 Set 类的常用API 2.0 HashSet 集合的说明 2.1 从 HashSet 集合的底层原理来解释是如何实现该特性 2.2 HashSet 集合的优缺点 2.3 深入理解 HashSet 集合去重的机制 2.4 如何快速编写已经重写好的 hashCode 和 equals 方法 3.0 Tree…

空中计算(Over-the-Air Computation)学习笔记

文章目录 写在前面 写在前面 本文是论文A Survey on Over-the-Air Computation的阅读笔记&#xff1a; 通信和计算通常被视为独立的任务。 从工程的角度来看&#xff0c;这种方法是非常有效的&#xff0c;因为可以执行孤立的优化。 然而&#xff0c;对于许多面向计算的应用程序…

【Arduino TFT】基于 ESP32S3 S7789 240x240 TFT实现的SD2 天气时钟

忘记过去&#xff0c;超越自己 ❤️ 博客主页 单片机菜鸟哥&#xff0c;一个野生非专业硬件IOT爱好者 ❤️❤️ 本篇创建记录 2023-10-21 ❤️❤️ 本篇更新记录 2023-10-21 ❤️&#x1f389; 欢迎关注 &#x1f50e;点赞 &#x1f44d;收藏 ⭐️留言&#x1f4dd;&#x1f64…

口袋参谋:如何找蓝海词?带动店铺搜索流量!

​为什么店铺没流量&#xff1f;很多新手商家在优化标题的时候从来不找词&#xff0c;凭着自己的想象做标题&#xff0c;这种情况很难获得流量。 要想获得更多的流量&#xff0c;符合产品属性的蓝海词是我们当属首选&#xff0c;不用和红海词去竞争&#xff0c;更不用和比较有…

java springboot+VUE OA企业办公自动化系统前后端分离开发mysql数据库web结构java编程计算机网页源码maven项目

一、源码特点 springboot VUE OA企业办公自动化系统是一套完善的完整信息管理类型系统&#xff0c;结合springboot框架和VUE完成本系统 后端采用mybatis进行数据库交互&#xff0c;对理解JSP java编程开发语言有帮 助系统采用springboot框架&#xff08;MVC模式开发&#xff…

Pandas数据处理分析系列4-数据如何清洗

Pandas-数据清洗 ①缺失值处理 使用fillna()函数将缺失值替换为指定的值或使用插值方法填充缺失值 示例:df.fillna(0) #将缺失值替换为0 import pandas as pddf1=pd.read_excel("销售表.xlsx") # 检查每列是否缺失 print(df1.isna) 效果如下: import pandas as …

std::string_view概念原理及应用

概念 使用const string&作为参数是先使用字符串字面量编译器会创建一个临时字符串对象然后创建std::string。 或者一个函数提供char*和const string&参数的两个版本函数&#xff0c;不是优雅的解决方案。 于是需要一个只使用内存不维护内存的类。 原理 在visual s…

数据结构——三路划分(快排优化)

刷Leetcode时遇到的问题&#xff0c;用普通的快排去跑&#xff0c;发现有问题。 普通的Hoare或者其他的快排好像都没有直接解决掉这个问题&#xff0c;当一个数重复出现的时候&#xff0c;用普通的快排效率其实并没有那么高。所以&#xff0c;这也是普通快排的缺点之一。 所以&…

STM32F4X之GPIO

一、GPIO概述 主控芯片信息如下&#xff1a; 主频&#xff1a;168MHZ内核&#xff1a;ARM-M4FLASH:1MSRAM:192KB引脚&#xff1a;100GPIO:82电压&#xff1a;1.8~3.6V 1.1GPIO概念及其作用 GPIO概念&#xff1a;通用输入输出(General Purpose Input Output)&#xff0c;主要作用…

解决报错【error: Microsoft Visual C++ 14.0 or greater is required】

当我们在环境中pip install某些python的依赖包时,直接pip install有时可能出现如下报错: error: Microsoft Visual C++ 14.0 or greater is required. Get it with "Microsoft C++ Build Tools": https://visualstudio.microsoft.com/visual-cpp-build-tools/,这说…

Linux搭建Redis环境

1. 基础环境 名称说明CentOS 7.6Linux操作系统版本redis-5.0.0.tar.gzRedis二进制安装包 2. 服务安装 服务端路径&#xff1a;usr/loacl/redis/redis-server客户端路径&#xff1a;usr/loacl/redis/redis-cli # 解压二进制包 [rootzhouwei resource]# tar -zxvf redis-5.0.…

IntelliJ IDEA 2023版本 Debug 时没有Force Step Into 按钮解决方法

IntelliJ IDEA 2023版本 Debug 时没有Force Step Into 按钮解决方法 force step into作用是能够去查看原码&#xff0c; 新版本idea默认移除了这个按钮&#x1f622; 那么让我们来把它找出来叭✋ 但是我们可以通过设置&#xff0c;使用step into就可以进入系统方法。 1.单击…

【TensorFlow1.X】系列学习笔记【入门二】

【TensorFlow1.X】系列学习笔记【入门二】 大量经典论文的算法均采用 TF 1.x 实现, 为了阅读方便, 同时加深对实现细节的理解, 需要 TF 1.x 的知识 文章目录 【TensorFlow1.X】系列学习笔记【入门二】前言神经网络的参数神经网络的搭建前向传播反向传播 总结 前言 学习了张量、…

react dispatch不生效的坑

一、前言 最近写react antd项目&#xff0c;在A页面中使用了dispatch方法&#xff0c;然后B页面中嵌套A页面&#xff0c;没有问题&#xff1b; 但是在C页面中嵌套A页面的时候&#xff0c;就发现dispatch方法没有执行&#xff0c;也不报错&#xff0c;就很奇怪&#xff1b; 还…

论坛介绍|COSCon'23 开源硬件(H)

众多开源爱好者翘首期盼的开源盛会&#xff1a;第八届中国开源年会&#xff08;COSCon23&#xff09;将于 10月28-29日在四川成都市高新区菁蓉汇举办。本次大会的主题是&#xff1a;“开源&#xff1a;川流不息、山海相映”&#xff01;各位新老朋友们&#xff0c;欢迎到成都&a…

navicate16在M1芯片运行问题

问题描述&#xff1a;本人M1芯片的mac&#xff0c;最近升级macOS14系统后&#xff0c;navicate15就总是闪退&#xff0c;如图 于是就安装了16的版本&#xff0c;但是16的版本不支持m1芯片电脑&#xff0c;如下图所示 于是就有了下面的操作&#xff0c;虽然能够使用了&#xff0…

【论文解读】The Power of Scale for Parameter-Efficient Prompt Tuning

一.介绍 1.1 promote tuning 和 prefix tuning 的关系 “前缀调优”的简化版 1.2 大致实现 冻结了整个预训练模型&#xff0c;并且只允许每个下游任务附加k个可调令牌到输入文本。这种“软提示”是端到端训练的&#xff0c;可以压缩来自完整标记数据集的信号&#xff0c;使…

数据图册页面(左边一列图片缩略图,右边展示图片大图)

最近要写这么一个页面&#xff0c;左侧一列图片缩略图&#xff0c;点击左侧缩略图后有选中效果&#xff0c;然后右侧展示图片原图&#xff0c;还能够左右翻页查看。 最后写了一个demo出来&#xff0c;demo还不是很完善&#xff0c;需要自己修改&#xff0c;后面我也给出了修改建…

发挥服务器的无限潜能:创意项目、在线社区和更多

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 ✨特色专栏&#xff1a…

ICMPv6与NDP

1. ICMPv6简介 ICMP概述 Internet控制消息协议ICMP (Internet Control Message Protocol)是IP协议的辅助协议。 ICMP协议用来在网络设备间传递各种差错和控制信息&#xff0c;对于收集各种网络信息、诊断和排除各种网络故障等方面起着至关重要的作用。 ICMP差错检查 ICMP …