底层视角看C语言

文章目录

  • main函数很普通
    • main函数之前调用了什么
    • main函数和自定义函数的对比
  • 变量名只为人而存在
  • goto是循环的本质
  • 指针变量
    • 指针是一个特殊的数字
      • 汇编层面看指针
    • 数组和指针
      • 数组越界问题
        • 低端地址越界
        • 高端地址越界
    • 引用就是指针

main函数很普通

main函数是第一个被调用的函数吗?在用户视角看来main函数的确程序的入口,但是在CPU视角下,main函数仅仅只是一个普通函数,和用户自定义的其他函数没有任何的区别。

main函数之前调用了什么

Linux环境:

_start->__libc_start_main->main

在这里插入图片描述

每一个Linux进程的入口函数都是_start,_start是一段直接由汇编语言编写的函数,它负责的工作就是把程序的命令行参数以及环境变量压入栈中,此时环境变量和参数一起存放在一个数组中

为了把环境变量单独提取出来,_start紧接着会调用__lib_start_main函数构建一张环境变量表,并进行一些全局变量的初始化工作,随后再进入main函数执行用户程序,再main函数退出时进行收尾操作例如全局变量的释放。这么一来,main函数似乎也只不过是一个被调用的函数,它只是默认被注册为用户代码的入口而已,也就是说,用户代码入口不一定非要是main函数

main函数和自定义函数的对比

一直以来我们编写C/C++程序时都是约定俗成地添加一个main函数来启动程序(因为不这么做往往会报错),这种情况一度让不少人认为main函数具有特殊的地位,能够得到CPU的青睐,其实不然,CPU眼中main函数啥也不是就是很普通的函数

int main(){return 0;
}
int func(){return 0;
}

通过汇编观察main和func的区别,会发现它们所对应的汇编指令居然完全一致

main:push rbpmov rbp, rspmov eax, 0pop rbpret
func:push rbpmov rbp, rspmov eax, 0pop rbpret

2个函数所做的操作都是一样的

  1. 建立函数栈帧 push rbp / mov rbp,rsp
  2. 将返回值拷入寄存器 mov eax,0
  3. 释放函数栈帧并返回 pop rbp / ret

gcc有一个命令可以改变用户代码的入口,使得用户指定其他函数作为程序起点

gcc -nostartfiles -efunc test.c

意思是编译test.c不使用系统的标准启动文件,将程序起点设置为func函数;一般不推荐这么做,因为使用标准启动文件代表着你需要自己为func瞻前顾后,这无疑是在自找麻烦

变量名只为人而存在

变量对程序员来说并不陌生,我们无时无刻都在使用变量帮助我们记忆,因为一个好的变量名可以大大提高源代码的可读性;尽管如此,对于可执行文件来说它并不需要存储所谓的变量名(release模式编译链接),CPU只需要知道一个逻辑地址就可进行读写操作,也就是说在发布模式编译链接时所有的变量名都会被转换称逻辑地址。

因此,我们可以给出关于变量的定义:=变量就是逻辑地址的一个别名,它向上以字符串形式以供人阅读记忆,向下被转成地址值供CPU访存

int a=0;
int main(){a=2;return 0;
}

所对应的汇编文件

main:push   rbpmov    rbp,rspmov    DWORD PTR [rip+0x0],0x2        # e <main+0xe>//将立即数0x2写入到rip值偏移量为0的位置,DWORD PTR标识4字节mov    eax,0x0pop    rbpret

可以看出a=2这条代码所对应的汇编是mov DWORD PTR [rip+0x0],0x2,CPU只需要通过几个逻辑地址相对寻址就可以确定内存的哪个位置需要被赋值为2

goto是循环的本质

虽然说不鼓励在编写C/C++程序时随意的使用goto,但是不代表goto不值得探究,早期的循环其实都是通过goto语句来实现的,只不过随着程序越来越大,过多的goto语句打破了程序的结构性使得源码难以维护,进而衍生出了结构性更强的for、while、do语句,它们都是在底层实现上都继承的goto的机制

void test_for(){for(int i=0;i<10;++i){}
}
void test_while(){int i=0;while(i<10) ++i;
}
void test_do(){int i=0;do{}while(++i<10);
}
void test_goto(){int i=0;goto L1;
L2:if(i<10) goto L1;return ;
L1:++i;goto L2;
}

对应的汇编代码

test_for:
//...mov DWORD PTR [rbp-4], 0jmp .L2
.L3:add DWORD PTR [rbp-4], 1
.L2:cmp DWORD PTR [rbp-4], 9jle .L3
//...
test_while:
//...mov DWORD PTR [rbp-4], 0jmp .L5
.L6:add DWORD PTR [rbp-4], 1
.L5:cmp DWORD PTR [rbp-4], 9jle .L6
//...
test_do:
//...mov DWORD PTR [rbp-4], 0
.L8:add DWORD PTR [rbp-4], 1cmp DWORD PTR [rbp-4], 9jle .L8
//...
test_goto:
//...mov DWORD PTR [rbp-4], 0jmp .L10
.L14:nop
.L10:add DWORD PTR [rbp-4], 1nopcmp DWORD PTR [rbp-4], 9jle .L14
//...

除了标签值不一样外,可以说基本上是一模一样

jmp指令是无条件跳转,对应进入循环体

cmp指令作作比较,add指令对应+1

jle指令是有条件跳转,负责继续or结束循环

指针变量

指针可以说是C语言的精髓所在,正是因为指针,使得C语言称为最灵活的高级语言,它使得用户可以自由的对一个内存区域进行读写(读写是否合法是另一码事),为了更好的理解指针变量,我们把指针变量这个名词拆解为指针+变量,变量上面提过它是地址的别名,指针其实就是一个地址值,因此所谓的定义一个指针变量的本质就是在一块内存空间上写入一个地址值(这和写入一个普通数没有什么区别)

指针是一个特殊的数字

地址值的本质就是数字,只不过它可以被用于访存(解引用),CPU可以先通过读取存放指针的那一块内存得到其中的地址值,再解引用该地址读写内存

在这里插入图片描述

汇编层面看指针

int a=1;
int main(){int* p=&a;*p=2;int** pp=&p;*pp=0;return 0;
}
main:
//...mov QWORD PTR [rbp-16], OFFSET FLAT:a //把a的地址写入地址[rbp-16]处,QWORD PTR标识8字节mov rax, QWORD PTR [rbp-16] //读取rbp-16地址处的值放入寄存器rax(a的地址)mov DWORD PTR [rax], 2 //解引用lea rax, [rbp-16]mov QWORD PTR [rbp-8], rax mov rax, QWORD PTR [rbp-8] mov QWORD PTR [rax], 0
//...

无论是几级指针的解引用,本质上都没有什么不同,都是从一块内存中获得另一块内存的位置进行访存

数组和指针

C语言中所有的数组传参最终都会退化成指针传参,因此传入多大的数组,最终在一个函数内部所看到的都是一个大小固定的指针

void fun1(int arr[5]){arr[3]=1;}
void fun2(char arr[100]){arr[3]=1;}
void fun3(double arr[1024]){arr[3]=1;}

汇编代码

fun1:
//...mov QWORD PTR [rbp-8], rdimov rax, QWORD PTR [rbp-8]add rax, 12 //3*4mov DWORD PTR [rax], 1
//...
fun2:
//...mov QWORD PTR [rbp-8], rdimov rax, QWORD PTR [rbp-8]add rax, 3 //3*1mov BYTE PTR [rax], 1
//...
fun3:
//...mov QWORD PTR [rbp-8], rdimov rax, QWORD PTR [rbp-8]add rax, 24 //3*8movsd xmm0, QWORD PTR .LC0[rip]movsd QWORD PTR [rax], xmm0
//...

数组索引操作的本质就是解引用,因此例子中的三个函数等价于

void fun1(int* arr){arr[3]=1;}
void fun2(char* arr){arr[3]=1;}
void fun3(double* arr){arr[3]=1;}

所谓的索引操作只不过是一个偏移量,用于指针的加减操作(指针变量加1减1的跨度取决于指向的类型,如果是int就移动4字节,char就移动1字节,double则是8字节)

数组越界问题

指针作为C语言的精髓,同时也是C语言最危险的一面,原则上一旦获取到地址就可以进行访存,但不能保证目标地址的数据是否可以被安全覆盖,如果一旦不小心将一些重要的内存空间刷新就有可能导致进程崩溃甚至更严重的后果。这种行为称之为野指针非法寻址,所谓野指针就是一个不应该被读写内存空间的地址,野指针最容易出现的场景就是数组越界。

虽然数组越界问题很危险,不过好在随着编译器进步,大部分数组越界问题都能在编译阶段得到拦截。

低端地址越界
void func1(){int a[2];a[1]=1;a[0]=2;a[-1]=3;a[-2]=4;
}
void func2(){int b[4];b[3]=1;b[2]=2;b[1]=3;b[0]=4;
}
int main(){func1();printf("have a good day\n");return 0;
}

很明显func1中存在数组越界访问的问题,但是它可能可以运行不会有段错误(可以看到have a good day,高版本编译器在编译阶段就直接报错),之所以正常运行的原因是因为虽然func1对一个非法区域进行了写入操作,但是碰巧这一块区域没有任何有效数据,所以不会出错

汇编代码

func1:
//...mov DWORD PTR [rbp-4], 1mov DWORD PTR [rbp-8], 2mov DWORD PTR [rbp-12], 3mov DWORD PTR [rbp-16], 4
//...
func2:
//...mov DWORD PTR [rbp-4], 1mov DWORD PTR [rbp-8], 2mov DWORD PTR [rbp-12], 3mov DWORD PTR [rbp-16], 4
//...

如果编译可以通过,通过查看汇编代码可以知道-1、-2索引操作偷偷地拓展了数组的长度使之变成4,并且是向低地址拓展的(rbp保存栈底指针,栈向低地址增长),这种越界称为低端地址越界,它可能不会造成程序崩溃,但大概率会影响到结果正确性(因为func1修改了本不属于它的栈空间,这可能造成其他局部变量数据失效)

高端地址越界

为了更好地就是高端地址越界,需要先稍微了解一下函数栈帧概念,每一个函数有一个栈空间称为栈帧,函数中所需要的局部变量都存储在栈帧中,当被调函数返回时需要销毁栈帧,CPU的指令寄存器恢复至主调函数。因此在建立新的函数栈帧时必须保存当前的指令地址以供后续返回,这个返回地址通过紧邻着被调函数栈帧的栈底。如果被调函数意外的修改了这里的值,就会发生意外(意外地执行恶意代码或段错误);这种越界称为高端地址越界

void evil(){//恶意代码printf("evil\n");exit(1);//让进程意外结束
}
void func1(){int a[2];a[2]=(int)evil; //将返回值设置为一个恶意函数//a[2]=0,将返回值设置为0地址处,这里没有合法指令就报段错误a[1]=1;a[0]=2;
}
int main(){func1();printf("have a good day\n");return 0;
}

(如果编译可以通过)main函数调用func1后进程就结束了,没有输出have a good day,即func1回不到main了,转而走到evil
在这里插入图片描述

引用就是指针

C++中引入的引用是对指针操作的简化,但其实只是在语法层面上做了一层封装和少量限制(没有多级引用和空引用,引用二次引用其他对象)

void f1(int* x){*x=1;}
void f2(int& x){x=1;}
void f3(int&& x){x=1;}

汇编代码

f1(int*):
//...建立栈帧mov QWORD PTR [rbp-8], rdimov rax, QWORD PTR [rbp-8]mov DWORD PTR [rax], 1
//...释放栈帧
f2(int&):
//...mov QWORD PTR [rbp-8], rdimov rax, QWORD PTR [rbp-8]mov DWORD PTR [rax], 1
//...
f3(int&&):
//...mov QWORD PTR [rbp-8], rdimov rax, QWORD PTR [rbp-8]mov DWORD PTR [rax], 1
//..

通过汇编可以很明显的看到f1和f2的赋值操作完全一致,引用也是通过获得地址后解引用才能实现对外部变量的修改,左值引用和右值引用在汇编实现上没有什么区别,只不过对于右值引用的修改是针对于一个被延长声明周期的临时对象做修改。

一句话描述引用:引用变量就是一个指针,对于引用变量的修改就是解引用操作

————————————————————————————

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

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

相关文章

Linux内核USB2.0驱动框架分析--USB设备枚举过程

一 USB特点 1.1 USB协议版本介绍&#xff1a; USB1.0/1.1&#xff08;low/fullspeed&#xff09;&#xff1a;传输速率最大为12Mbps&#xff0c;是较早的USB协议版本。 USB2.0&#xff08;highspeed&#xff09;&#xff1a;传输速率最大为480Mbps&#xff0c;相比USB1.0/1.1…

bert-base-uncased处理文档

1.安装必要的库 确保安装 transformers 和 torch 库&#xff1a; pip install transformers torch 2.加载本地 BERT 模型和分词器 由于已将模型和分词器下载到本地&#xff0c;可以指定文件路径加载。确保路径与本地文件结构一致。 from transformers import BertTokenizer…

【快速入门】Kafka的安装部署

目录 一、我的集群环境介绍 二、集群部署 1、解压安装包 2、修改解压后的文件名称 3、进入到/opt/installs/kafka3 目录&#xff0c;修改配置文件 4、分发安装包 5、分别在 bigdata02 和 bigdata03上修改配置文件 6、配置环境变量 7、启动集群 8、停止集群 三、本文用…

Windows安装tensorflow的GPU版本

前言 首先本文讨论的是windows系统&#xff0c;显卡是英伟达&#xff08;invida&#xff09;如何安装tensorflow-gpu。一共需要安装tensorflow-gpu、cuDNN、CUDA三个东西。其中CUDA是显卡的驱动库&#xff0c;cuDNN是深度学习加速库。 安装开始前&#xff0c;首先需要安装好c…

深度学习(神经网络)中模型的评估与性能度量

深度学习&#xff08;神经网络&#xff09;中模型的评估与性能度量 1.在现实任务中&#xff0c;我们往往有多种学习算法可供选择&#xff0c;那么我们如何评判一个模型的好坏呢&#xff1f;理想的解决方案是对模型的泛化误差进行评估&#xff0c;选择泛化误差最小的&#xff1…

mysql做数据统计图表常用的sql语句 部门人数 工龄 学历 年龄 性别 在职人员 兴趣分析查询

做统计有时候挺头疼的&#xff0c;sql改来改去还是达不到想要的效果&#xff0c;这里分享一下最近写的sql&#xff0c;感兴趣的朋友可以看看了解一下&#xff0c;sql语句代码都有注释。 这里只分享sql查询语句&#xff0c;前端图表用的是Echarts。 首先看一下统计的效果图 1、…

无人机之姿态融合算法篇

无人机的姿态融合算法是无人机飞行控制中的核心技术之一&#xff0c;它通过将来自不同传感器的数据进行融合&#xff0c;以实现更加精确、可靠的姿态检测。 一、传感器选择与数据预处理 无人机姿态融合算法通常依赖于多种传感器&#xff0c;包括加速度计、陀螺仪、磁力计等。这…

phpstudy 使用php8.2.9版本报错问题

phpstudy 使用php8.2.9版本报错问题 1、如果php8的扩展控制面板开启无效的话&#xff0c;可以手动开启试试 2、php有报错日志&#xff1a; Fatal error: Directive ‘track_errors’ is no longer available in PHP in Unknown on line 0 在切换php版本到更高版本时在终端查…

SpringBoot开发——SpringBoot3.3 中实现多端口监听

文章目录 1、项目环境与依赖配置2、配置多端口监听3、编写配置类实现多端口监听4、为每个端口创建独立的配置类4.1 8081 端口配置类4.2 8082 端口配置类 5、控制器类定义5.1 8080 端口的控制器&#xff08;保持原有配置&#xff09;5.2 8081 端口的控制器5.3 8082 端口的控制器…

Git分支

目录​​​​​​​ 一、分支是什么 二、查看、创建分⽀(git branch) 查看分支 创建分支 三、切换分支(git checkout&#xff0c;git checkout -b) 四、合并分支(git merge、git log查看合并情况) 五、删除分支&#xff08;git branch -d []&#xff09; 六、合并冲突&…

sigmoid和softmax有什么区别,softmax的本质是最大熵?

本文理论依据王木头的视频&#xff1a; softmax是为了解决归一问题凑出来的吗&#xff1f;和最大熵是什么关系&#xff1f;最大熵对机器学习为什么非常重要&#xff1f;_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1cP4y1t7cP/?spm_id_from333.999.0.0&vd_sourc…

stm32 挂死定位(死循环)

1&#xff0c;调式程序时先看初始化和轮询点灯流程&#xff0c;如果灯没有闪烁则表明程序进入了死循环。 2&#xff0c;程序调式进入调式debug接口&#xff1b; 3&#xff0c;打断点依次运行&#xff1b; 4&#xff0c;查看寄存器&#xff1b; LR在异常后通常为0xFFFFFFFx&am…

ZFC in Lean 之 前集及其成员关系(S, ∈)是良创的(Well-founded)

基于前文&#xff0c;对前集&#xff08;S&#xff0c;pre-set&#xff09;、其成员关系&#xff08;∈&#xff0c;membership&#xff09;&#xff0c;以及良创&#xff08;Well-Founded&#xff09;的定义&#xff0c;此文&#xff0c;分析&#xff08;S, ∈&#xff09;是良…

【暴刷力扣】59. 螺旋矩阵 II

题目 给你一个正整数 n &#xff0c;生成一个包含 1 到 n2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。 题解 leetcode 大部分题解写的不知道都是什么——代码非常杂乱。 还是直接放上紫书&#xff08;《算法竞赛入门指南》&#xff09;…

vue3+ts+element-ui实现的可编辑table表格组件 插入单行多行 组件代码可直接使用

最近需求越来越离谱&#xff0c;加班越来越严重&#xff0c;干活的牛马也越来越卑微。写了一个可编辑表格&#xff0c;并已封装好组件&#xff0c;可直接使用。 基于这位大佬的 动态表格自由编辑 方法和思路&#xff0c;于是参考和重写了表格&#xff0c;在基础上增加和删除了…

zxing生成、解析二维码,条形码

1、maven依赖 <!--zxing依赖--><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.1.0</version></dependency><dependency><groupId>com.google.zxing</groupI…

JQuery设置Cookie操作,设置、获取、删除三种方法

//触发条件 当用户点击或者操作时需要设置cookie时 //方法里面定义了三个处理cookie的方法 $(document).ready(function(e) {$("#btnsetcookie").click(function() {setCookie("Demo", "我的示例Cookie数据", 2); //设置cookie});$("#btn…

bert-base-uncased使用

1.下载模型 https://github.com/google-research/bert?tabreadme-ov-file 2.下载config.json和pytorch_model.bin https://huggingface.co/google-bert/bert-base-uncased/tree/main 3.解压缩到同一文件夹 4.代码测试 from transformers import BertModel,BertTokenizerBER…

【人工智能】阿里云PAI平台DSW实例一键安装Python脚本

阿里云的DSW实例自带的镜像很少而且并不好用&#xff0c;所以我在这里写三个一键编译安装Python3.8&#xff0c;Python3.9&#xff0c;Python3.10的Shell脚本。 安装Python3.8 wget https://www.smallbamboo.cn/install_python38.sh && chmod x install_python38.sh …

每日科技资讯:2024年11月09日【龙】农历十月初九 ---文末送书

目录 1.史上最强游戏CPU&#xff01;9800X3D首发评测2.苹果喊话iPhone 13和14钉子户&#xff1a;16方方面面都升级了3.加拿大政府下令 TikTok 关闭该国业务&#xff0c;但应用仍可以继续访问4.OpenAI 刚刚花了超过 1000 万美元购买了Chat.com5.Max 加入打击密码共享行列6.微软可…