程序环境和预处理(1)

文章目录

  • 目录
    • 1. 程序的翻译环境和执行环境
    • 2. 详解编译+链接
      • 2.1 翻译环境
      • 2.2 编译本身也分为几个阶段
      • 2.3 运行环境
    • 3. 预处理详解
      • 3.1 预定义符号
      • 3.2 #define
        • 3.2.1 #define 定义标识符
        • 3.2.2 #define 定义宏
        • 3.2.3 #define 替换规则
        • 3.2.4 #和##
        • 3.2.5 带副作用的宏参数
        • 3.2.6 宏和函数对比

目录

  • 程序的翻译环境
  • 程序的执行环境
  • 详解:C语言程序的编译+链接
  • 预定义符号介绍
  • 预处理指令 #define
  • 宏和函数的对比
  • 预处理操作符#和##的介绍
  • 命令定义
  • 预处理指令 #include
  • 预处理指令 #undef
  • 条件编译

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

在ANSI C的任何一种实现中,存在两个不同的环境。

第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。
第2种是执行环境,它用于实际执行代码。

计算机是能够执行二进制指令的,但是我们写出的C语言代码是文本信息,计算机不能直接理解。

翻译环境:C语言代码 —> 二进制的指令(放在可执行程序中)

执行环境:执行二进制的代码

2. 详解编译+链接

2.1 翻译环境

翻译环境

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

我们也可以通过代码来观察:

//add.cint Add(int x, int y)
{return x + y;
}
//test.c#include <stdio.h>extern int Add(int, int);int main()
{int a = 10;int b = 20;int c = Add(a, b);printf("%d\n", c);return 0;
}

目标文件和可执行程序

2.2 编译本身也分为几个阶段

翻译环境详解

  1. 预处理阶段
    预处理阶段
  2. 编译阶段
    编译阶段
  3. 汇编阶段
    汇编阶段

解释一下符号汇总、符号表:
代码
ELF文件格式
readelf结果
在这个目标文件里,确实能看到一些符号(都是全局的


接下来,我们用两个文件来举例子:符号汇总解释

这有什么用呢?

如果没有定义Add函数,那么在链接的时候就定位不到这个函数,就会发生链接错误,生成不了可执行程序。

2.3 运行环境

程序执行的过程:

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

3. 预处理详解

3.1 预定义符号

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

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

举个例子:

#include <stdio.h>int main()
{printf("%s\n", __FILE__);printf("%d\n", __LINE__);printf("%s\n", __DATE__);printf("%s\n", __TIME__);//printf("%d\n", __STDC__);//当前VS是不支持ANSI	Creturn 0;
}

对代码进行预处理之后:
预定义符号

3.2 #define

3.2.1 #define 定义标识符

语法:
#define name stuff

#include <stdio.h>#define M 100
#define STR "abc"
#define FOR for(;;)
#define reg register//为 register这个关键字,创建一个简短的名字int main()
{printf("%d\n", M);printf("%s\n", STR);FOR;//死循环return 0;
}

预处理之后:
#define定义标识符


int main()
{int d = 0;switch (d){case 1:break;case 2:break;case 3:break;}return 0;
}

以上代码还可以这样写:

#define CASE break;caseint main()
{int d = 0;switch (d){case 1:CASE 2:CASE 3:}return 0;
}

#include <stdio.h>//如果定义的 stuff 过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\tdate:%s\t\time:%s\n" , __FILE__, __LINE__,\__DATE__, __TIME__)int main()
{DEBUG_PRINT;return 0;
}

提问:

在define定义标识符的时候,要不要在最后加上 ;

比如:

#define M 100;int main()
{int a = M;return 0;
}

预处理(1)
这样的代码看上去是没有问题的,但是这样写是非常容易出错的:

#define M 100;int main()
{int a = 0;int b = 0;if (a > 5)b = M;elseb = -1;return 0;
}

预处理(2)
if 语句后面默认只能跟一条语句,这里再加上一个 ; 就变成了两条语句,这就意味着下面的 else 不知道和谁匹配了。

因此,建议不要加上 ; ,这样容易导致问题。

3.2.2 #define 定义宏

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

下面是宏的申明方式:

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

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

#include <stdio.h>#define MAX(x,y) ((x)>(y)?(x):(y))
int main()
{int a = 2;int b = -2;int c = MAX(a, b);printf("c=%d\n", c);return 0;
}

预处理之后:
预处理(3)
提示:

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

预处理(4)
应该这样写:

#include<stdio.h>#define SQUARE(x) ((x) * (x))int main()
{int a = 3;int r = SQUARE(a + 2);printf("r=%d\n", r);return 0;
}

预处理(5)
应该这样写:

#include<stdio.h>#define DOUBLE(x) ((x) + (x))int main()
{int a = 3;int r = 10 * DOUBLE(a);printf("r=%d\n", r);return 0;
}
3.2.3 #define 替换规则

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

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

预处理(6)
注意:

  1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
  2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
#include<stdio.h>#define M 3int main()
{printf("M=%d\n", M);//M=3return 0;
}
3.2.4 #和##

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

首先我们看看这样的代码:

#include <stdio.h>int main()
{printf("hello world\n");//hello worldprintf("hello ""world\n");//hello worldreturn 0;
}

我们发现字符串是有自动连接的特点的。


我们想实现这样一个功能:

#include <stdio.h>int main()
{int a = 20;printf("the value of a is %d\n", a);int b = 15;printf("the value of b is %d\n", b);float f = 4.5f;printf("the value of f is %f\n", f);return 0;
}

我们可以用宏来实现(函数做不到):

#include <stdio.h>#define PRINT(n, format) printf("the value of "#n" is "format"\n", n)int main()
{int a = 20;//printf("the value of a is %d\n", a);PRINT(a, "%d");int b = 15;//printf("the value of b is %d\n", b);PRINT(b,"%d");float f = 4.5f;//printf("the value of f is %f\n", f);PRINT(f, "%f");return 0;
}

代码中的 #n预处理“n”


##的作用:

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

#include <stdio.h>#define CAT(x,y) x##yint main()
{int Class110 = 2024;printf("%d\n", CAT(Class, 110));//2024printf("%d\n", Class110);//2024return 0;
}

预处理(7)

3.2.5 带副作用的宏参数

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

int main()
{int a = 10;//int b = a + 1;//b=11, a=10int b = ++a;//b=11, a=11return 0;
}

x+1;//不带副作用
x++;//带有副作用

MAX宏可以证明具有副作用的参数所引起的问题:

#include <stdio.h>#define MAX(x, y) ((x)>(y)?(x):(y))int main()
{int a = 5;int b = 6;int c = MAX(a++, b++);//int c = ((a++) > (b++) ? (a++) : (b++));//        5       6               7//c=7//b=8//a=6printf("c = %d\n", c);printf("a = %d\n", a);printf("b = %d\n", b);return 0;
}
3.2.6 宏和函数对比
#include <stdio.h>//1
#define MAX(x, y) ((x)>(y)?(x):(y))//2
int Max(int x, int y)
{return (x > y ? x : y);
}int main()
{int a = 5;int b = 6;int c = MAX(a, b);//int c = Max(a, b);printf("c = %d\n", c);printf("a = %d\n", a);printf("b = %d\n", b);return 0;
}

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

那为什么不用函数来完成这个任务?

原因有二:

  1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多,所以宏比函数在程序的规模和速度方面更胜一筹。
    宏和函数对比

  2. 更为重要的是函数的参数必须声明为特定的类型,所以函数只能在类型合适的表达式上使用;反之,这个宏则可以适用于整形、长整型、浮点型等可以用 > 来比较的类型。宏是类型无关的。

宏的缺点:
当然和函数相比,宏也有劣势的地方:

  1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。(代码如果特别长,那么编译的压力就会很大,因为在编译的时候会对代码做各种各样的处理,如:语法分析、词法分析等等)
  2. 宏是没法调试的。
  3. 宏由于类型无关,也就不够严谨。
  4. 宏可能会带来运算符优先级的问题,导致程容易出现错。

宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。

#define MALLOC(num, type) (type*)malloc(num*sizeof(type))int main()
{int* p = (int*)malloc(126 * sizeof(int));//malloc(126, int);//errint* p = MALLOC(126, int);//int* p = (int*)malloc(126 * sizeof(int));return 0;
}

宏和函数的一个对比
宏和函数的区别

对于第三点的一个例子:

//1
#define MAX(x, y) ((x)>(y)?(x):(y))//2
int Max(int x, int y)
{         //5      6return (x > y ? x : y);
}int main()
{int c = MAX(2 + 3, 6);//int c = ((2+3)>(6)?(2+3):(6))c = Max(2 + 3, 6);return 0;
}

补充:
宏有自己的优势,当然也有劣势

函数也有自己的优势,也有劣势

能不能有一个函数既具有函数的好,也具有宏的好呢?

inline — 内联

内联函数

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

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

相关文章

Windows Server 2012 IIS中发布ASP.NET CORE项目

服务器安装IIS&#xff1a; 微软官网下载SDK&#xff1a; 下载Runtime官网&#xff1a;https://dotnet.microsoft.com/download/dotnet-core 安装成功重启IIS&#xff1a; VS发布项目&#xff1a;

osmnx笔记:从OpenStreetMap中提取点和边的shp文件(FMM文件准备内容)

1 导入库 import osmnx as ox import time from shapely.geometry import Polygon import os import numpy as np 2 提取Openstreetmap 的graph Gox.graph_from_place(Huangpu,Shanghai,China,network_typedrive,simplifyTrue) ox.plot_graph(G) 3 提取graph中的点和边 gdf…

leetcode 2583.二叉树中的第K大层和

题目 给你一棵二叉树的根节点 root 和一个正整数 k 。 树中的 层和 是指 同一层 上节点值的总和。 返回树中第 k 大的层和&#xff08;不一定不同&#xff09;。如果树少于 k 层&#xff0c;则返回 -1 。 注意&#xff0c;如果两个节点与根节点的距离相同&#xff0c;则认为…

无公网IP情况下如何远程查看本地群晖NAS存储的文件资源

文章目录 前言本教程解决的问题是&#xff1a;按照本教程方法操作后&#xff0c;达到的效果是前排提醒&#xff1a; 1. 搭建群晖虚拟机1.1 下载黑群晖文件vmvare虚拟机安装包1.2 安装VMware虚拟机&#xff1a;1.3 解压黑群晖虚拟机文件1.4 虚拟机初始化1.5 没有搜索到黑群晖的解…

进程1——进程与线程——day09

今天&#xff0c;主要讲一下进程的一些基本概念和一些接口 首先是进程的基本概念&#xff1a; 1.进程: 程序&#xff1a;存放在外存中的一段数据组成的文件 进程&#xff1a;是一个程序动态执行的过程,包括进程的创建、进程的调度、进程的消亡 2.进程相关命令: 1.top 动态…

【Nginx】微信小程序后端开发、一个域名访问多个服务

【Nginx】微信小程序后端开发、一个域名访问多个服务 1. 微信小程序后端开发 对于后端程序员&#xff0c;其实你们的职责就是干老本行&#xff0c;即写接口和服务&#xff0c;让前端能够访问你的接口就行&#xff0c;必要时需要查看微信小程序开发文档去向微信服务器发请求。…

回归预测 | Matlab实现SSA-BiLSTM-Attention麻雀算法优化双向长短期记忆神经网络融合注意力机制多变量回归预测

回归预测 | Matlab实现SSA-BiLSTM-Attention麻雀算法优化双向长短期记忆神经网络融合注意力机制多变量回归预测 目录 回归预测 | Matlab实现SSA-BiLSTM-Attention麻雀算法优化双向长短期记忆神经网络融合注意力机制多变量回归预测预测效果基本描述程序设计参考资料 预测效果 基…

零基础手把手教你创建微信小程序(二)·创建第一个微信小程序以及了解小程序代码的构成

零基础手把手教你创建微信小程序&#xff08;一&#xff09;微信小程序开发账号的注册以及开发者工具的安装和使用-CSDN博客 目录 ​编辑 1. 创建微信小程序 1.1 基本信息 1.2 在模拟器上查看项目效果 1.3 在真机上预览项目效果 1.4 主界面的5个组成部分 1.4.1 菜单…

NPM私服搭建(verdaccio)

官网地址&#xff1a;https://verdaccio.org/ 概述 Verdaccio 是一个流行的 Node.js 包管理器的代理工具&#xff0c;它允许您在本地或私有网络上轻松地创建和管理 npm 包仓库。通过 Verdaccio&#xff0c;开发团队可以建立自己的 npm 包仓库&#xff0c;以更好地控制和管理其依…

【力扣】Z 字形变换,模拟 + 直接构造

Z 字形变换原题地址 方法一&#xff1a;利用二维矩阵模拟 对于特殊情况&#xff0c;Z 字形变换后只有一行或只有一列&#xff0c;则变换后的字符串和原字符串相同。 对于一般情况&#xff0c;我们可以考虑按照题目要求&#xff0c;把字符串按照 Z 字形存储到二维数组中&…

做抖店想要快速起店怎么办?产品和流量是关键!新手可收藏!

大家好&#xff0c;我是电商小布。 在抖音小店开通完成后&#xff0c;大家考虑的第一件事情&#xff0c;一定是小店如何能够快速出单&#xff0c;成功起店。 店铺出单的重点&#xff0c;其实就在小店的运营上。 那么这么多的环节&#xff0c;关键点在哪呢&#xff1f; 答案…

大学生多媒体课程学习网站thinkphp+vue

开发语言&#xff1a;php 后端框架&#xff1a;Thinkphp 前端框架&#xff1a;vue.js 服务器&#xff1a;apache 数据库&#xff1a;mysql 运行环境:phpstudy/wamp/xammp等开发背景 &#xff08;一&#xff09; 研究课程的提出 &#xff08;二&#xff09;学习网站的分类与界定…

前端页面之间传输数据 localStorage

效果 发送方 接收方 localStorage 的使用 // 保存数据 localStorage.setItem(key, value); // 获取数据 localStorage.getItem(key);发送方 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>登录<…

【深蓝学院】移动机器人运动规划--第6章 模型预测控制(MPC)与运动规划--笔记

0. Outline 1. Reactive Control&#xff08;反应式控制&#xff09; 控制学中的 “Reactive Control” 通常指的是一种控制策略&#xff0c;它依赖于系统对特定事件或变化的即时反应&#xff0c;而不是按照预定的计划或策略行动。这种控制往往是基于当前的传感器输入来做出决…

c编译器学习07:minilisp编译器改造(debug模式支持调试)

问题 原版的minilisp编译器不支持argv输入测试&#xff0c;不方便单步调试。 代码改造目标是既不改变原有程序的各种功能&#xff0c; 又能支持个人习惯的vs单步debug模式。 CMakeLists.txt变更 定义DEBUG宏 解决单步调试源码定位偏差问题 cmake_minimum_required(VERSION …

【Android安全】Windows 环境下载 Android 源码

准备环境 安装 git 安装 Python 硬盘剩余容量最好大于 100G 打开 Git Bash&#xff0c;用 git 克隆源代码仓库 git clone https://android.googlesource.com/platform/manifest.git //没有梯子使用清华源 git clone https://aosp.tuna.tsinghua.edu.cn/platform/manifest.git…

RabbitMQ 部署方式选择

部署模式 RabbitMQ支持多种部署模式&#xff0c;可以根据应用的需求和规模选择适合的模式。以下是一些常见的RabbitMQ部署模式&#xff1a; 单节点模式&#xff1a; 最简单的部署方式&#xff0c;所有的RabbitMQ组件&#xff08;消息存储、交换机、队列等&#xff09;都运行在…

TensorRT及CUDA自学笔记003 NVCC及其命令行参数

TensorRT及CUDA自学笔记003 NVCC及其命令行参数 各位大佬&#xff0c;这是我的自学笔记&#xff0c;如有错误请指正&#xff0c;也欢迎在评论区学习交流&#xff0c;谢谢&#xff01; NVCC是一种编译器&#xff0c;基于一些命令行参数可以将使用PTX或C语言编写的代码编译成可…

新手如何自己建网站的详细步骤?-网站建设

新手如何自己建网站的详细步骤&#xff1f;-网站建设 我们选择了白嫖雨云的二级域名 浏览器输入https://www.rainyun.com/z22_ 创建账号然后选择一个你喜欢的子域名我建议后缀选择ates.top的 选择自定义地址&#xff0c;类型选择cname 现在要选择记录值了&#xff0c;有a&…

linux内核原理--页高速缓存,回写,页框回收

1.页高速缓存 我们主要分析下磁盘文件的页高速缓存 struct address_space {struct inode *host; struct radix_tree_root page_tree; spinlock_t tree_lock;unsigned int i_mmap_writable;struct prio_tree_root i_mmap; struct list_head i_mmap_nonlinear;spinlock_t i_…