由浅入深学习 C 语言:Hello World【提高篇】

目录

 

引言

1. Hello World 程序代码

2. C 语言角度分析 Hello World 程序

2.1. 程序功能分析

2.2 指针

2.3 常量指针

2.4 指针常量

3. 反汇编角度分析 Hello World 程序

3.1 栈

3.2 函数用栈传递参数

3.3 函数调用栈

3.4 函数栈帧

3.5 相关寄存器

3.6 相关汇编指令

3.7 汇编代码分析

3.7.1 invoke_main() 函数调用了 main 函数

3.7.2 main 函数的栈帧建立和销毁过程

3.7.3 "Hello World\n" 字符串在内存中是以 assii 码的形式保存


 

引言

        本篇是 Hello World 程序提高篇,默认读者是有 C 语言编程基础,0 基础建议先阅读这篇博文的姐妹篇之由浅入深学习 C 语言:Hello World【基础篇】-CSDN博客

1. Hello World 程序代码

#include <stdio.h>   int main(int argc, const char *argv[])
{for (int i = 0; i < argc; i++)    // 打印命令行参数printf("argv[%d] = %s\n", i, argv[i]);printf("Hello World\n");	return 0;        
}

c10cabd517a34496915f8ab949fe1555.png

2. C 语言角度分析 Hello World 程序

2.1. 程序功能分析

1.  #include <stdio.h>   // 预处理器指令,用于把 stdio.h 文件包含进来

2. int main(int argc, const char *argv[])        // 主函数,是 C 程序的入口函数

(1)函数名前的 int 是函数的返回值

(2)argc 是函数的第一个参数,参数类型是 int

  • 该参数表示的是命令行参数个数,传给这个参数的值是第二个参数 argv 数组的元素个数。
  • 一个 C 程序至少有一个命令行参数,这个命令行参数是 可执行文件的绝对路径名或相对路径名

(3)argv 是函数的第二个参数,参数类型是 const char* 数组,也就是说这个参数是一个数组,数组的每一项存的数据类型是 const char* (常量字符指针),指向每一个命令行参数字符串的首地址。

比如,我们的 Hello World 程序,在 linux 平台下,生成 main.out 可执行文件。 

  • ./main.out  // 不带参数运行程序
  • argc = 1
  • argv 数组有 1 个元素,这个元素存储的就是 "./main.out" 这个字符串在内存中的首地址

343c4198edfd48f680245ec55563351b.png

  • ./main.out 111 222 // 带 2 个参数运行程序
  • argc = 3
  • argv 数组有 3 个元素,第一个元素存储的是 "./main.out" 这个字符串在内存中的首地址,第二个元素存储的是 "111" 这个字符串在内存的首地址,第三个元素存储的是 "222" 这个字符串在内存的首地址 

ee1d32a9355549d18c9ec25e22adc2de.png

2.2 指针

        在 C 语言中,不管什么数据类型的指针,实际就是一块大小固定的内存,这块内存存的值是另一块内存单元的地址,也就是说这块内存存在的意义是为了指向另一块内存单元,所以我们把它称作指针。

  • 32 位程序中,这块内存大小是 4 个字节
  • 64 位程序中,这块内存大小是 8 个字节       
#include <stdio.h>int main(int argc, const char* argv[]) {int* pi = NULL;			// int* 指针double* pd = NULL;		// double* 指针 printf("pi size = %d\n", sizeof(pi));	// 32位程序,pi size = 4; 64位程序,pi size = 8printf("pd size = %d\n", sizeof(pd));	// 32位程序,pd size = 4; 64位程序, pd size = 8return 0;
}

e8d9eea74b1a413f95a4f3f2c655816d.png

7ac597045f884f1db7ecd8533dbf0b8e.png

2.3 常量指针

        常量指针,本质是一个指针,用 const 修饰的指针是常量指针,也就是说这个指针指向另一块存储常量的内存单元,所以我们不能通过常量指针来修改它指向的另一块内存单元的值。

#include <stdio.h>int main(int argc, const char* argv[]) {for (int i = 0; i < argc; i++) {// *argv[i] = '1';	// error: 常量指针,这个指针指向的内存单元的值是常量,不能被修改printf("argv[%d] = %s\n", i, argv[i]);}return 0;
}

        所以,main 函数的第二个参数声明为 const char* (常量字符指针) 可以防止我们在实际的开发中,不小心修改了 argv 数组里的元素指向的内存单元的值。

2.4 指针常量

        指针常量,本质是一个常量,用指针类型修饰的常量是指针常量,常量就必须声明的时候初始化,初始化后整个程序运行期间都不能再修改。

#include <stdio.h>int main(int argc, const char* argv[]) {int a = 1;int* const pi = &a;int b = 2;// pi = &b;		// error: 常量不能被修改return 0;
}

3. 反汇编角度分析 Hello World 程序

3.1 栈

        在计算机科学中,栈是一种后进先出(LIFO,last in first out)的数据结构,往栈中写数据,叫做入栈(push),将栈顶数据从栈中弹出,叫做出栈(pop)。

3.2 函数用栈传递参数

        函数用栈传递参数的原理是由调用者通过 push 指令将需要传递给被调用者的参数压入栈中,被调用者从栈中取得参数。

3.3 函数调用栈

        C 语言,所有函数的调用,是通过栈来实现的,当函数被调用时,其返回地址、参数以及局部变量会被压入栈中。当函数返回时,这些信息会从栈中弹出,以恢复到函数被调用之前的状态。我们可以称维护所有函数调用形成的这个栈空间为函数调用栈。

        特别要注意的是,一个 C 程序的栈是往下生长的,即栈底在高地址,栈顶在低地址,入栈,栈顶地址会变小,出栈,栈顶地址会变大。

3.4 函数栈帧

        在 C 语言中,从一个函数的进入,到这个函数返回,形成的栈空间,我们称为函数栈帧。函数栈帧不是固定不变的,而是随着函数的功能,栈帧空间可能随时在变大或缩小。

3.5 相关寄存器

  • ebp: extended base pointer,扩展基址指针寄存器,一般用于当进入一个函数时,存放该函数的栈帧基址(栈帧的栈底)。
  • esp: extended stack pointer,扩展栈指针寄存器,存放函数栈的栈顶
  • ebp 和 esp 配合使用,共同协作维护着正在运行的函数的栈帧
  • ebx: extended base register,扩展基址寄存器,特别是在数组和字符串操作中,它可以用来存储数组或字符串的基地址。此外,在调用操作系统函数时,ebx 有时也用于传递参数。
  • esi: extended source index register,扩展源索引寄存器,在字符串操作中非常有用。它通常用于存储源字符串或数据数组的起始地址,在字符串指令(如 stos 等)中自动递增,以便按顺序处理数据。
  • edi: extended destination index register,扩展目的索引寄存器,与 esi 相对应,在字符串操作中用于存储目标字符串或数据数组的起始地址。与 esi 相似,edi 也在字符串指令中自动递增,以接收来自源地址的数据。
  • ecx: extended count register,扩展计数寄存器,在循环操作中尤其重要。它存储了循环的迭代次数,很多指令(如以 rep 为前缀的字符串操作指令)都会递减 ecx 的值来控制循环次数。
  • eax: extended accumulator register,eax 寄存器通常用作累加器,在算术和逻辑运算中扮演主要角色。它经常用于存储操作数、结果以及中间值。在函数调用中,eax 常用于存放函数的返回值。

3.6 相关汇编指令

  • mov: 数据传送指令,比如:mov eax, 3        ;将 3 这个立即数传送给 eax 寄存器
  • push: 往栈中压入数据,CUP操作原理:(1) esp - 存储压入栈数据占用的字节数。 (2) 修改栈顶数据为要压入栈中的数据。比如:push eax        ;将 eax 寄存器的数据压入栈中
  • pop: 从栈顶弹出数据, CUP操作原理:(1) 栈顶数据弹出栈  (2) esp + 从栈顶弹出的数据占用的字节数。比如:pop eax        ;将栈顶数据弹出,送到 eax 寄存器中
  • sub: 减法指令
  • add: 加法指令
  • rep: 在汇编语言中,rep 的作用是根据 ecx 的值,循环执行跟在其后的指令,直到 ecx = 0 时为止。
  • cmp: 根据操作结果,设置标志寄存器相关位的值,其他相关指令就可以拿到寄存器相关位的值进行相关操作
  • stos: 字符串操作指令,比如:stos dword ptr es:[edi],是将 eax 的值传送给 es:[edi] 为首地址的 4 个字节单元中
  • xor: 二进制异或操作,即 1 xor 0 = 1
  • call: 转到标号处执行指令
  • ret: 跟 call 配合,控制 cpu 返回到调用该函数的指令的下一条指令执行

3.7 汇编代码分析

int main(int argc, const char* argv[]) {
00E517E0  push        ebp       ;调用者的栈帧基址入栈
00E517E1  mov         ebp,esp   ;构建自己的栈帧基址
00E517E3  sub         esp,0C0h  ;栈顶往上移 0C0h 字节
00E517E9  push        ebx       ;调用者的 ebx 入栈,esp = esp-4
00E517EA  push        esi       ;调用者的 esi 入栈,esp = esp-4
00E517EB  push        edi       ;调用者的 edi 入栈,esp = esp-4
00E517EC  mov         edi,ebp   ;edi = ebp
00E517EE  xor         ecx,ecx   ;等价于 mov ecx, 0; 但 xor 指令更高效
00E517F0  mov         eax,0CCCCCCCCh  
00E517F5  rep stos    dword ptr es:[edi]    ;rep 的作用是根据 cx 的值,循环执行跟在其后的指令,直到 cx = 0 时为止。;stos 串传送指令,相当于 mov es:[di], eax ;df = 0, edi = edi + 4; df = 1, edi = edi - 4;printf("Hello World\n");
00E517F7  push        offset string "Hello World\n" (0E57B30h)  ;传递给 printf 函数的参数入栈,esp = esp-4;offset:取得字符串 "Hello World\n" 在内存的首地址
00E517FC  call        _printf (0E510CDh)    ;调用 printf 函数 
00E51801  add         esp,4     ;esp = esp+4,即销毁传递给 printf 函数的参数 "Hello World\n" 在内存的首地址占用的空间return 0;
00E51804  xor         eax,eax   ;函数返回值放在 eax 寄存器,该指令相当于 mov eax, 0; 但 xor 指令更高效
}
00E51806  pop         edi       ;还原调用者的 edi,esp = esp+4
00E51807  pop         esi       ;还原调用者的 esi,esp = esp+4
00E51808  pop         ebx       ;还原调用者的 ebx,esp = esp+4
00E51809  add         esp,0C0h  ;栈顶往下移 0C0h 字节
00E5180F  cmp         ebp,esp   ;计算 ebp-esp,然后根据结果对 cpu 的标志寄存器进行设置;目的是让下一条指令 call __RTC_CheckEsp,检测该函数栈帧建立前跟销毁该函数栈帧后的esp 是否一致
00E51811  call        __RTC_CheckEsp (0E5123Fh)  ;检测标志寄存器相关位的值,从而判断 ebp 跟 esp 是否相等
00E51816  mov         esp,ebp   ;恢复调用者的栈顶
00E51818  pop         ebp       ;恢复调用者的栈顶基址
00E51819  ret                   ;调用者通过 call 指令调用函数会 push eip, 自己必须 ret 指令,pop eip

3.7.1 invoke_main() 函数调用了 main 函数

        查看 VS2019 的调用堆栈窗口,我们可以知道,框架的 invoke_main() 函数调用了我们

main 函数。

2ec43735487547f08e4924959ed146a4.png

3.7.2 main 函数的栈帧建立和销毁过程

(1)进入主函数栈的初始状态

f03d9aaa5fd64cb5b5c96841f35ff0d0.png4fa2193bbcc947cb983cd639cf627966.png

(2)cpu 执行 push ebp

33b196a5785743b39944a5a050b6476c.png12ba992e03c84b85912a26af42625d09.png

(3)cpu 执行 mov ebp, esp

5cf00ce3a3254d288c604d30ee9f61cc.png

a86a4aa44cca4d18a847190d53ba18af.png

(4)cpu 执行 sub esp, 0C0h

f99fe424b97c46aeb8d040337084e5ba.pngd913a8d5b15a4b9c98cc7b8100ec743d.png

(5)cpu 执行 push ebx

ed128ef92ec3417da700a12c4757ca65.png

d32d4afeb10c4c10b9313079269d1f9a.png

(6)cpu 执行 push esi

eb96338c34d14b1f8cf0cb3cc7d86aad.png

8898c819c8b241cd85d4b2eebdf19af4.png

(7)cpu 执行 push edi

b43f8a6d84614b4a9211954bfad86a64.png

86fc1c6d2c994abfb138fc09e495207f.png

(8)cpu 执行以下指令对栈没影响

00E517EC  mov         edi,ebp   ;edi = ebp
00E517EE  xor         ecx,ecx   ;等价于 mov ecx, 0; 但 xor 指令更高效
00E517F0  mov         eax,0CCCCCCCCh  
00E517F5  rep stos    dword ptr es:[edi]    ;rep 的作用是根据 cx 的值,循环执行跟在其后的指令,直到 cx = 0 时为止。;stos 串传送指令,相当于 mov es:[di], eax ;df = 0, edi = edi + 4; df = 1, edi = edi - 4;

(9)cpu 执行 push offset string "Hello World\n"

627fcb502d2f471fad92f669ddec32d8.png

164f4a73b71c4f49b067414b47cb6181.png

(10)cpu 执行 call _printf 指令,cpu 进入 _printf 函数执行完该函数包含的指令返回后,栈又回到了跟没执行 _printf 函数一样的状态。

(11)cup 执行 add esp, 4

84a94f0d64f648beb5a4ff2ce7553c56.png

519c1483a3504b7884a8038c213f9251.png

(12)cpu 执行 xor eax, eax 对栈没影响

(13)cpu 执行 pop edi

        892bc76573ff41e982ac102dde6817bc.png

ed74f3a335804e8cb054d6765967d030.png

(14)cpu 执行 pop esi

7430c7a140da4185a04ceeaf504177e6.png

13c4cedd5fb143369971dfbd9d2822b8.png

(15)cpu 执行 pop ebx

ffd41c4548b643df945173d2e998fcf9.png

b2de40e4933543a394a61e1b091dfa0e.png

(16)cpu 执行 add esp, 0C0h

4ea74ab0d8734eba8250b0855414f11f.png

ab296b41a7b5441bb612ac48c093b13f.png

(17)cpu 执行以下指令对栈没影响

00E5180F  cmp         ebp,esp   ;计算 ebp-esp,然后根据结果对 cpu 的标志寄存器进行设置;目的是让下一条指令 call __RTC_CheckEsp,检测该函数栈帧建立前跟销毁该函数栈帧后的esp 是否一致
00E51811  call        __RTC_CheckEsp (0E5123Fh)  ;检测标志寄存器相关位的值,从而判断 ebp 跟 esp 是否相等

(18)cpu 执行 mov esp, ebp

4d39a3eab99e4c5f85511775f00132eb.png

6c1367cb3bb84796aee351e84cc933be.png

(19)cpu 执行 pop ebp

67734e7a8f674623ba0e989156052b98.png

94705d53d3894a5ba2d941e34b3c221e.png

此时,我们看到函数栈的状态回到了跟刚进入该函数时的初始状态是一致的。

3.7.3 "Hello World\n" 字符串在内存中是以 assii 码的形式保存

        fdc1ff0925384ae09fc325091a05e8f1.png

大写字母 H:assii 码为 72,十六进制表示为 0x48

小写字母 e:assii 码为 101,十六进制表示为 0x65

小写字母 l:assii 码为 108,十六进制表示为 0x6c

小写字母 o:assii 码为 111,十六进制表示为 0x6f

大写字母 W:assii 码为 87,十六进制表示为 0x57

小写字母 r:assii 码为 114,十六进制表示为 0x72

小写字母 d:assii 码为 100,十六进制表示为 0x64

换行符 \n:assii 码为 10,十六进制表示为 0x0a

 

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

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

相关文章

优化学习管理:Moodle和ONLYOFFICE文档编辑器的完美结合

目录 前言 一、什么是 Moodle 1、简单快速插入表单字段 3、免费表单模板库 4、开启无缝协作 三、在Moodle中集成ONLYOFFICE文档 四、在Moodle安装使用ONLYOFFICE 1、下载安装 2、配置服务器 3、在Moodle中使用ONLYOFFICE 文档活动 五、未来展望 写在最后 前言 在当今教育科技飞…

JVM垃圾回收算法:标记-清除算法 、复制算法、 标记-整理算法、 分代收集算法

文章目录 引言I 标记回收算法(Mark-Sweep)算法不足II 复制算法(Copying)III 标记整理算法(Mark-Compact)IV 分代收集(以上三种算法的集合体)内存划分新生代算法:Minor GC老年代算法V 查看JVM堆分配引言 垃圾回收(Garbage Collection,GC) Java支持内存动态分配、…

Flask+LayUI开发手记(六):树型表格的增删改查

树型表格的增删改查功能与数据表格的是完全一致&#xff0c;就是调用layui-form表单组件实现数据输入再提交&#xff0c;比较大的区别是树型节点的编辑&#xff0c;都需要有上级节点的输入&#xff0c;而这个上级节点的展示&#xff0c;必须是以树型方式展示出来。当然&#xf…

语音控制开关的语音识别ic芯片方案

语音控制开关是一种基于语音识别技术的设备&#xff0c;它通过内置的语音识别芯片&#xff0c;将用户的语音指令转化为电信号&#xff0c;从而实现对设备的控制。例如在智能家居设备上的应用&#xff0c;通常需要连接到家庭的Wi-Fi网络上&#xff0c;以便与智能手机或智能音箱等…

golang RSA 解密前端jsencrypt发送的数据时异常 crypto/rsa: decryption error 解决方法

golang中 RSA解密前端&#xff08;jsencrypt&#xff09;发来的密文后出现 "crypto/rsa: decryption error" &#xff0c; 这个问题首先需要确认你的私匙和公匙是否匹配&#xff0c; 如果匹配 那检查入参数据类型&#xff0c; 前端发送来的rsa加密后的数据一般都是…

bbr 随机 phase 的麻烦与 inflight 守恒算法的动机

bbr 有个要点&#xff0c;要把 probebw 的 phase 错开&#xff1a; static void bbr_reset_probe_bw_mode(struct sock *sk) {struct bbr *bbr inet_csk_ca(sk);bbr->mode BBR_PROBE_BW;bbr->cycle_idx CYCLE_LEN - 1 - prandom_u32_max(bbr_cycle_rand);bbr_advance…

Java项目:基于SpringBoot+mysql在线拍卖系统(含源码+数据库+答辩PPT+毕业论文)

一、项目简介 本项目是一套基于SSM框架mysql在线拍卖系统 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、界面美观、操作简单、功能齐全、…

Java 面试题:HTTP版本演变--xunznux

文章目录 HTTP版本演变HTTP/0.9HTTP/1.0HTTP/1.1新引入&#xff1a;问题&#xff1a;长连接是什么&#xff1a;管道网络传输&#xff1a;队头阻塞是什么&#xff1f;解决http队头阻塞的方法&#xff1a;HTTP1.1常见性能问题为解决HTTP1.1性能问题而提出的常见优化手段 HTTP/21、…

【河北航空-注册安全分析报告-无验证方式导致安全隐患】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 1. 暴力破解密码&#xff0c;造成用户信息泄露 2. 短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉 3. 带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造…

RS485与USB3.1电路

USB2.0最高也就480M&#xff0c;而USB3.0轻松到达5Gbps&#xff1a; DTU远程控制&#xff1a; DTU&#xff08;数据传输单元&#xff09;通常用于将数据从现场设备传输到远程服务器&#xff0c;常用于物联网、工业控制、远程监控等场景。它可以通过GPRS/4G、Wi-Fi、以太网等方…

Python网络爬虫模拟登录与验证解析

内容导读 使用Selenium模拟登录 使用Cookies登录网站 模拟表单登录网站 爬虫识别简单的验证码 实例解析 一、使用Selenium模拟登录 1、为什么要模拟登录 在互联网上存在大量需要登录才能访问的网站&#xff0c;要爬取这些网站&#xff0c;就需要学习爬虫的模拟登录。对…

TC-RAG: Turing-Complete RAG--图灵完备的检索增强

摘要&#xff1a; 在提升领域特定的大语言模型&#xff08;LLMs&#xff09;的方法中&#xff0c;检索增强生成&#xff08;RAG&#xff09;技术作为一种有前景的解决方案&#xff0c;可以缓解诸如幻觉、知识过时以及在高度专业化查询中专业知识有限等问题。然而&#xff0c;现…

WPF- vs中的WPF应用项目模板 如何自己实现

读书笔记 1. 单个 c#文件的 空白window应用程序 (只展示了一个button按钮) 2.C#文件 和xml文件 的空白window程序 .xml文件作为程序的资源 (只一个button按钮) 3. xmal和c#共同编译 形如使用VS 创建WPF应用项目模板 1.新建一个wpf空白项目 ,添加一个主c#文件 和xaml文件(属…

【C++ Primer Plus习题】7.2

问题: 解答: #include <iostream> using namespace std;#define MAX 10int input(float* grade, int len) {int i 0;for (i 0; i < len; i){cout << "请输入第" << i 1 << "个高尔夫成绩(按0结束):";cin >> grade[i]…

【二叉树进阶】--- 前中后序遍历非递归

Welcome to 9ilks Code World (๑•́ ₃ •̀๑) 个人主页: 9ilk (๑•́ ₃ •̀๑) 文章专栏&#xff1a; 算法Journey 本篇博客我们将来了解有关二叉树前中后序遍历的非递归版本。 &#x1f3e0; 前序遍历 要迭代非递归实现二叉树的前序遍历&#xff0c;首先还…

【Android】MotionLayout实现动画效果

【Android】MotionLayout实现开场动画 在移动应用开发中&#xff0c;动画不仅仅是美化界面的工具&#xff0c;它更是提升用户体验的关键手段。Android 平台一直以来都提供了丰富的动画框架&#xff0c;但随着应用复杂性的增加&#xff0c;开发者对动画的需求也变得更加复杂和多…

如何通过WinRAR软件有效禁止RAR压缩包内文件的修改

RAR压缩包作为一种广泛使用的文件格式&#xff0c;凭借其高压缩比和强大的功能&#xff0c;成为了许多用户保存和传输文件的首选。然而&#xff0c;在某些情况下&#xff0c;我们可能希望确保RAR压缩包内的文件不被随意修改或删除&#xff0c;以维护文件的安全性和完整性。本文…

【网络】数据链路层-MAC帧

数据链路层-以太网与ARP协议 文章目录 1.数据链路层2.以太网2.1什么是以太网2.2MAC帧格式 3.ARP协议3.1为什么有ARP协议&#xff1f;3.2ARP的定位3.3ARP协议工作流程3.4ARP数据格式 4.RARP协议 1.数据链路层 数据链路层是网络协议栈中最底层的内容&#xff0c;而在之前对其他…

恶劣天气下的目标检测新突破:多尺度退化建模与特征融合策略

更多优质内容&#xff0c;请关注公众号&#xff1a;智驾机器人技术前线 1.论文信息 论文标题&#xff1a;Degradation Modeling for Restoration-enhanced Object Detection in Adverse Weather Scenes 作者&#xff1a;Xiaofeng Wang, Xiao Liu, Hong Yang, Zhengyong Wang, …

阿里PAI-ChatLearn:大规模 Alignment高效训练框架正式开源

导读 ChatGPT是OpenAI开发的基于大型语言模型(LLM)的聊天机器人&#xff0c;以其令人惊叹的对话能力而迅速火爆并被广泛采用。ChatGPT 成功背后得益于大型语言模型生成领域的新训练范式&#xff1a;RLHF (Reinforcement Learning from Human Feedback)&#xff0c;即以强化学习…