C编译器剖析_1.5 结合C语言来学汇编_指针、数组和结构体

C语言入门:从Hello World到指针与内存管理的编程基石 编程语言是计算机科学的基础,它定义了人与机器沟通的规则。在众多编程语言中,C语言以其独特的定位脱颖而出:它作为一门中级语言,在高级语言的抽象性和底层硬件的直接控制之间取得了精妙平衡。这种设计哲学使其成为系统编程的基石,通过指针直接操作内存,实现了对硬件资源的精细控制,从而在操作系统、嵌入式开发和高性能计算等场景中展现出不可替代的技术价值。理解C语言的核心语法,如变量、函数和流程控制,是掌握编程逻辑的第一步。而深入其灵魂——指针与内存管理,则是理解程序如何在计算机中运行的关键。本文以C语言入门为核心,通过He 阅读详情

    让我们再来看一份C代码,及其经UCC编译器编译后产生的主要汇编代码,如图1.33所示,其中包含了数组、指针和结构体。


图1.33 数组、指针和结构体

     按照C的语义,图1.33第9行的C代码是对局部数组number的初始化,需要把number[0]初始化为2015,而数组中的其他元素皆被初始化为0。UCC编译器采取的翻译方法是:先调用memset函数来把数组number所占的内存空间清0,然后再把number[0]设为2015,如图1.33的第17至24行所示。C库函数memset的API如下所示:

void *memset(void *s, int c, size_t n);

    按照“C调用约定 Calling Convention”,参数需要从右到左入栈,即先要把n入栈,再把c入栈,之后才是目标地址s。第17行的$64对应参数n,表示数组number所占的内存大小为64字节,因为每个int占4字节,而number共有16个整数;而第18行的$0对应参数c,表示我们要把指针s所指的大小为n字节的内存全部设为0;第19行用于取内存单元-64(%ebp)的地址,第20行把这个地址入栈,即对应参数s,而-64(%ebp)所对应的内存空间正是UCC编译器为局部数组number在栈中分配的存储空间。第21行完成了对库函数memset的调用,第22行用于把刚才入栈的3个参数出栈,此处每个参数正好都占4个字节,所以总共占了12字节的栈空间。按照“C调用约定”,这个退栈操作需要由主调函数来完成。实际上,第17至22行也演示了如何在汇编语言中调用C库函数。

    而与第10行” ptr = &number[1];”对应的汇编代码为第25至27行,第25行把number[0]的地址存到寄存器eax中,因为number[1]与number[0]之间隔了一个占4字节的整数,所以第26行对eax进行了加4的操作,这样eax中的内容就是number[1]的地址,第27行把这个地址存到了全局变量ptr所对应的内存单元中。

    与第11行” *ptr = 2016;”对应的汇编代码在图1.33的第29至30行,第29行通过movl指令把内存单元ptr中存放的内容传送到寄存器ecx中,这样ecx中存放的数据就是number[1]的地址,第30行” movl $2016, (%ecx)”告诉汇编器,把ecx当作一个指针,把立即数2016存到ecx所指向的内存单元中,此指令执行完后,ecx寄存器没有发生变化,但ecx所指向的number[1]内存单元被设置为2016。第32至35行演示了“如何在汇编代码中,给结构体对象的成员域赋值”,在C语言中我们用的是” dt.year”,但在汇编中我们可以看到,与之对应的是第35行的”dt+8”,结构体成员域的名字year已经被偏移值offset所取代,此处偏移为8。由第2至第6行可知,在成员year的前面,还有date和month这两个成员,共占去了8字节,如果从0开始计数,则year正好处于第8个字节处。

    让我们再看一份“通过函数指针来间接调用函数等语句”的C程序,如图1.34所示。第19至22行的汇编代码完成了第11行”dt1 = dt2;”的功能,此处主要的目的是介绍寄存器esi和edi在指令rep movsb中的作用。第19到22行的汇编代码其实相当于如下C函数的功能,寄存器esi相当于参数src,寄存器名si是Source Index的缩写,起源地址的作用;而edi相当于参数dest,寄存器名di是Destination Index的缩写,起“目的地址”的作用;寄存器ecx则相当于参数n,表示要复制的字节数;真正进行复制操作的是第22行的汇编代码。结构体对象dt1由30个整数构成,每个整数4字节,所以共占了120字节,所以我们在第21行看到的立即数$120。

void *memcpy(void *dest, const void *src, size_t n);

 

 

图1.34     Function Call

   一般地,在C语言中,我们可以用这样的套路来理解类型为T的变量名var:变量var占一块大小为sizeof(T)的内存,在C程序中“直接使用变量名var”代表了C程序员想要读取或改变这块内存中的内容;在C程序中“在变量var前加一个&,即&var”代表了程序员想要读取这块内存对应的地址。

T  var;

    例如,对以下代码而言,在语句”a = b;”中,我们使用了变量名a和b,我们想表达的是把b的内容复制到a中,即读取变量b的内容,用来改变变量a的内容。在语句”ptr1 = &a;”中,”&a”表达了C程序员想要读取变量a的地址,”ptr1”位于赋值号的左侧,意味着我们想要“改变”ptr1变量中的内容。在语句”ptr2 = ptr1;”,我们仍可以沿用这个套路,使用变量名ptr1和ptr2,我们想表达的是把ptr1的内容复制到ptr2中,即读取变量ptr1的内容,用之改变变量ptr2的内容。

int a,b;

int * ptr1, * ptr2;

a = b;

ptr1 = &a;

ptr2 = ptr1;

    但这个套路也有一些例外,比如函数名和数组名。按上面的套路,通过“函数名f”,我们想访问的就是函数所占内存中的内容,这个“内容”当然就是代码区中的代码了,CPU要执行这个函数,实际上只要知道这块代码区的首地址就可以了。基于这样的出发点,让函数名代表函数所占内存的首地址,就合情合理了。而”&f”仍可沿用之前的套路来理解。所以图1.34的第8行可改为如下语句也是可以的。

FUNC * func = f;

    在后续分析UCC编译器源代码时,我们会看到编译器对函数名的特殊处理。当然对函数名f而言,sizeof(f)在C89标准中是非法的。在编译时,sizeof表达式是要作为一个常量来处理的,但很多时侯,我们在编译一个.c文件时,可能只在头文件中看到了函数f的声明,其函数定义根本就还没看到。在只知道函数接口,而不知道其定义的情况下,是无法知道函数要在代码区中占多大内存的。

    而数组名是另外一个特例,按前面的套路,数组名本应代表数组中的内容,这其实也是个不错的选择。因为这意味着,我们可以使用如下语句来实现两个数组对象的复制。

int arr1[1000];

int arr2[1000];

arr1 = arr2;

    但考虑到数组可能作为函数参数时的情况,若让数组名arr1来代表数组中的内容,则以下的函数调用中,形参arr就要占4000字节的栈空间。所以,让数组名arr1代表数组的首地址,就成了又一个合理的选择。而”&arr1”仍可沿用之前的套路来理解,这就有了”arr1”和”&arr1”代表的都是数组的首地址,但在C的类型系统中,两者却有不同的类型,arr1实际上代表的是数组第0个元素的地址,即&arr1[0],类型为”int * ”,而”&arr1”的类型实际上是” ARRAY1000  *”。

typedef  int  ARRAY1000[1000];

void  f(int arr[1000]);

f(arr1);

    最明显的区别是,(arr1+1)和 (&arr+1)这两个表达式的值差得非常之大。按照C语言指针运算的语义,对指向T类型的指针ptr而言,(ptr+1)代表的是“指向下一个类型为T的对象”,即(ptr)和(ptr+1)之间相差sizeof(T)。这里,sizeof(int)是4,而sizeof(ARRAY1000)是4000。

T  *  ptr;

    我们看到,图1.34第12行和第13行所对应的汇编代码在第23和24行,都是”call *func”,汇编指令call用于调用函数,”call  * func”意味着函数地址存在于变量func的内容中,全局变量func的地址在编译连接后就可确定,但变量func的内容是可能发生变化的,这就需要在程序运行时从变量func对应的内存单元中取出其内容,即其所保存的函数地址。而第25行的”call  f”中的函数地址f在编译连接时就已确定,在汇编指令中,f确实上相当于一个地址常量。

    到此,我们结合一些简单的C程序,把ucc\ucl\x86Linux.tpl中用到的有代表性的汇编代码作了介绍。回顾整个第1章,我们从语言、递归和文法入手,通过一个简单的例子ucc\examples\sc,介绍了如何由文法来编写语法分析器,如何在生成的语法树上进行“中间代码生成”,后续章节剖析的UCC编译器,还需要在中间代码的基础上产生汇编代码,所以在第1.5节,我们还介绍了UCC编译器所用到的主要汇编代码。

    经过第1章的热身运动,我们准备开始正式踏上UCC编译器剖析的漫漫长征路。所谓“工欲善其事,必先利其器”,在Ubuntu环境下可先安装wine,然后安装Source Insight等Windows平台的代码阅读工具。这种环境可能要被Linux的忠实爱好者鄙视,但从简单实用的角度出发,不失为一种不错的选择。边阅读ucc的源代码,边加上自己的一些注释和体会,看得兴起之时,再加上一些用于调试的代码,例如使用ucc\ucl\ucl.h的PRINT_DEBUG_INFO等宏定义来输出,来观察一下编译器的运行结果是否如我们所预期。如果感觉自己似乎发现潜在的Bug时,就写一些简单的测试代码用来触发UCC编译器的Bug。慢慢的,我们就会进入正轨了。世界上不存在完全健康的人,同样道理,稍复杂点的软件,或多或少都存在Bug。把软件产品改到没有bug再发布,几乎是不可能的。不然,Windows也不会经常需要打补丁了。诚如台湾作者龙应台所言,“所谓了解,就是知道对方心灵最深的地方的痛处,痛在哪里”。相信随着阅读的深入,你会发现一些UCC编译器的Bug。目录ucc\examples中包含了已发现的一些Bug,例如在文件ucc\examples\array\array.c的开头,包含了以下注释,其含义是array.c的代码是针对ucc编译器exprchk.c中的函数CheckPostfixExpression()。

/*****************************************************

         seeCheckPostfixExpression(AstExpression expr) in exprchk.c

 *****************************************************/

 

 

     最后,让我们以下面这段代码来结束第一章。再好的书,再好的资料,最多只能帮助我们少走弯路,让我们在无助时有个依靠。但是“有些事,只能一个人做。有些关,只能一个人过。有些路啊,只能一个人走”。

while(还没读懂ucc\examples\sc){

         阅读并上机运行ucc\examples\sc的代码

}

C语言指针本质:内存访问契约与调试实战 指针是C语言连接硬件与逻辑的核心机制,其本质并非简单的‘地址存储’,而是编译器、CPU与内存之间关于类型、地址和访问权限的底层契约。理解指针需从内存布局出发,掌握指针运算的类型缩放原理、野指针/悬空指针的物理成因,以及函数指针在调用约定下的真实行为。技术价值在于实现零开销抽象、DMA直连、中断向量跳转等硬实时操作;典型应用场景覆盖嵌入式驱动(如fm24cl64 I2C通信)、链表动态管理、Qt/GDB调试及STM32裸机开发。本文聚焦可验证的底层机制,结合GDB内存查看、offsetof偏移分析与汇编级追踪 阅读详情

相关推荐

C编译器剖析PDF文档_UCC162.3源代码

包含C编译器剖析PDF文档,及修改少量BUG后的编译器源代码UCC162.3, 欢迎访问对应博客http://blog.csdn.net/sheisc

逆向知识第十四讲,(C语言完结)结构体在汇编中的表现形式

              逆向知识第十四讲,(C语言完结)结构体在汇编中的表现形式 一丶了解什么是结构体,以及计算结构体成员的对其值以及总大小(类也是这样算) 结构体的特性   1.结构体(struct)是由一系列具有相同类型或不同类型的数据构成的数据集合   2.在C语言中,结构体(struct)指的是一种数据结构,是C语言中聚合数据类型(aggregate data type)...

weixin_30394633的博客 429

汽车底盘技术解析:从悬架调校到智能化,如何评价国产车底盘实力

汽车底盘作为决定车辆驾驶质感、安全性和耐久性的核心系统,其技术内涵远不止于悬架结构。从行驶系、转向系到制动系的协同工作,底盘调校是一门融合机械原理与工程实践的艺术。在电动化与智能化浪潮下,底盘技术正经历从被动适应到主动响应的变革,CDC可变阻尼减震器和空气悬架等技术的应用,让底盘具备了通过OTA升级实现性能优化的可能。国产汽车品牌如奇瑞、吉利等,通过平台化架构研发与调校经验积累,正推动底盘技术从“能用”向“好用”乃至“塑造独特驾驶风格”迈进。理解底盘技术,需超越简单的结构对比,关注其在实际路况中的滤震性、支

weixin_34413103的博客 387

关于指针(一)

自己感觉有意义值得记录的知识记录下来(平时习惯手写),跟大家分享下。关于指针(一)学习C语言最让人头疼的就是关于c语言指针问题,这个问题我也经常弄错,整理如下: 指针的定义就不累赘,看例子:int a = 20;意思是将20存入a变量的内存空间,那么这个变量在哪?(变量的地址)printf(“%d\n”, a); /*输出变量a的值20*/ printf(“%d\n”, &a); /*输出变量

代码从零开始 662

Python爬虫实战:5分钟搞定微信公众号文章阅读数、点赞数抓取(附完整代码)

本文详细介绍了如何通过Python爬虫技术自动化抓取微信公众号文章的阅读数和点赞数。文章从数据源分析入手,分步讲解了获取文章列表与提取单篇统计数据的完整流程,并提供了应对平台风控的策略与工程化整合方案,帮助开发者高效、合规地完成数据采集任务。

ansible6ops的博客 1336

卸载Office 16 Click-to-Run Extensibility Component详细步骤

卸载Office 16 Click-to-Run Extensibility Component详细步骤 第一步:按住键盘上的“Win+R”,打开运行窗口 在运行窗口输入“Installer”回车,打开Installer文件夹 第二步:在Installer文件夹红框最右侧,即“大小”后面,右击选择“其他”,找到“主题”,勾选,点击“确定”,添加主题列成功 第三步:在“主题”列找到“Office 16 Click-to-Run Extensibility Component

M_try的博客 3万+

c语言大写字母指针计数,关于C语言指针,这可能是史上最干最全的讲解啦(附代码)!!!...

原标题:关于C语言指针,这可能是史上最干最全的讲解啦(附代码)!!!文章字数:14000 干货指数:⭐⭐⭐⭐⭐指针对于C来说太重要。然而,想要全面理解指针,除了要对C语言有熟练的掌握外,还要有计算机硬件以及操作系统等方方面面的基本知识。所以本文尽可能的通过一篇文章完全讲解指针。为什么需要指针?指针解决了一些编程中基本的问题。✅指针的使用使得不同区域的代码可以轻易的共享内存数据。当然小伙伴们也可以通...

weixin_35917998的博客 614

Windows摄像头错误0xA00F4240根因与注册表修复指南

Windows摄像头错误码0xA00F4240常被误判为硬件故障,实则源于UWP应用模型下权限调用链断裂——核心是COM组件调用失败(HRESULT 0x80004003),本质反映Windows Runtime API对注册表策略键值(如CapabilityAccessManager\ConsentStore\webcam)的强依赖。该机制涉及服务状态、DWORD权限配置、ACL继承控制等多层校验,任一环节异常(如ValueLastModified时间戳损坏或ALL APPLICATION PACKAGE

weixin_31742871的博客 389

C/C++核心机制深度解析:指针、结构体与动态内存管理(面试精要)

在系统级编程领域,C/C++语言凭借对硬件的直接操作能力和高效的内存管理机制,长期占据主导地位。面试中,指针、结构体和动态内存管理作为三大核心考点,不仅考察候选人对语言特性的理解深度,更检验其工程实践能力和问题解决思维。本文将结合面试场景,深入剖析这三个关键领域的核心概念与实战技巧。

m0_74096349的博客 2191

用Docker-Compose三节点部署生产级Doris集群:从配置文件到故障转移

本文详细介绍了如何使用Docker-Compose部署一个生产级的三节点Apache Doris集群。内容涵盖从核心架构理解、配置文件编写到集群初始化验证的全过程,并重点讲解了如何实现高可用、数据持久化、健康检查以及故障转移等生产级运维关键点,为中小团队提供了一种在容器化便利性与系统稳定性间取得平衡的可靠部署方案。

kkk56的博客 857

CCS安装教程图解说明:可视化步骤快速上手

通过清晰的图解步骤详解CCS安装过程,帮助新手快速掌握环境搭建。结合ccs安装教程与实际操作界面,每一步都直观易懂,大幅降低学习门槛,提升配置效率。

weixin_42611310的博客 963

数组传参失效?C语言中数组名退化为指针的3个致命后果,90%程序员都忽略了

掌握C语言数组传参陷阱,避免常见错误。详解C语言数组名作为函数参数退化为指针的机制,揭示长度丢失、越界风险与遍历难题,提供安全传参方案。理解底层原理,提升代码健壮性,值得收藏。

PixelGlow的博客 892

JasperReport制作行内容合并的表格

效果图: 实现思路一: 交叉表 另一个思路: 普通表格 缺点:无法实现内容 垂直居中显示 准备工作 一、数据准备 DROP TABLE IF EXISTS `address_item_sex_value`; CREATE TABLE `address_item_sex_value` ( `id` int(11) NOT NULL AUTO_INCREM...

weixin_34148456的博客 2468

C语言入门:指针与数组的关系

数组名不是指针,可它偏偏总被当指针用,那它到底算个啥呢。我学C语言快两个月了,光是“a[3]到底怎么算出来的”这个问题,就反复查了三回标准文档,问了两个同学

u013489014的专栏 182

Cesium中级开发教程之五十二:气泡弹窗

支持多种交互方式触发气泡弹窗显示,如鼠标悬停、点击地图上的特定元素(像实体、标注、模型等)。例如,当用户鼠标悬停在一个表示城市的点上时,弹出显示该城市名称、人口等信息的气泡。:依据特定的数据条件或业务逻辑触发。比如,当某个区域的气象数据达到一定阈值时,自动弹出显示相关气象预警信息的气泡。

664

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

C语言入门教程,C语言学习教程(非常详细)

C语言中文网推出辅导班啦,包括「C语言辅导班、C++辅导班、算法/数据结构辅导班」,全部都是一对一教学:一对一辅导 + 一对一答疑 + 布置作业 + 项目实践 + 永久学习。QQ在线,随时响应! C语言是一门面向过程的编译型语言,它的运行速度极快,仅次于汇编语言。C语言是计算机产业的核心语言,操作系统、硬件驱动、关键组件、数据库等都离不开C语言;不学习C语言,就不能了解计算机底层。 这套「C语言入门教程」由站长亲自执笔,将多年的编程经验灌输其中,典型的实践派。这部教程已经发布了 5 年,经历了 5 次大

digitalkee的博客 8677

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

【C高级专题】指针才是C语言的精髓

一、指针 二、 指针带来的一些符号的理解 三、深入学习一下数组 四、 指针与数组的天生姻缘 五、 指针与强制类型转换

qq_52792304的博客 506

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

C语言指针与数组:从概念到实战的深度解析

指针是C语言中直接操作内存地址的核心概念,通过存储变量地址实现高效数据访问。其工作原理基于内存地址的算术运算和类型系统,在系统编程和性能优化中具有重要价值。数组名作为指针常量与普通指针变量存在本质区别,这种差异直接影响函数参数传递、内存操作和编译优化。在实际工程中,理解指针与数组的关系能有效避免内存越界和段错误,提升代码安全性和可维护性。本文通过对比普通指针变量、指针常量(数组名)和指向数组的指针,深入分析函数参数传递和内存操作等实战场景,帮助开发者掌握C语言内存管理的精髓。

weixin_34150830的博客 571

C语言指针与数组:核心概念与高效编程实践

指针和数组是C语言中最重要的基础概念,它们直接操作内存地址,为系统编程和性能优化提供底层支持。指针存储内存地址,通过解引用访问数据;数组则是连续内存块的抽象,二者通过指针算术实现高效遍历。这种机制在内存管理、数据结构实现和算法优化中具有关键作用,尤其适用于嵌入式开发、操作系统等需要精细控制内存的场景。理解指针与数组的差异(如sizeof行为、类型信息)能避免常见内存错误,而动态内存分配、多级指针等进阶用法则大幅提升编程灵活性。通过函数指针实现回调、利用柔性数组成员处理变长数据等技巧,展现了C语言在资源受限环

weixin_34005090的博客 310

C语言底层机制全解析

《C语言底层知识大全》系统梳理了C语言的核心底层机制,涵盖数据类型内存布局、指针操作、编译原理、函数调用栈帧、内存管理策略等关键内容。重点解析了硬件相关的字节序、数据对齐、原子操作等特性,并深入探讨多线程同步、内存屏障等并发编程要点。文章还包含编译器优化、汇编接口等进阶知识,以及内存池、对象池等实战技巧,为开发者提供从语言特性到系统级编程的完整知识图谱,帮助深入理解C程序的运行机制与性能优化方法。(149字)

本博客聚焦游戏开发技巧、创业实战心得及大学热门课程干货。这里既有技术深度,也有思维广度,无论你是开发者、创业者还是高校学子,都能在这里找到适合自己的成长之路! 1382

指针才是C的精髓

《C语言高级专题第3部分-4.3.指针才是C的精髓》 课程特点:深入浅出、具有可学习性、实例来说话 第一部分、章节目录 4.3.1.指针到底是什么? 4.3.2.指针带来的一些符号的理解 4.3.3.野指针问题 4.3.4.const关键字与指针 4.3.5.深入学习一下数组 4.3.6.指针与数组的天生姻缘 4.3.7.指针与强制类型转换 4.3.8.指针、数组与sizeof运算符 4.3....

huang714的专栏 279

大学生版保姆式学《C 和指针》——重新理解 C 语言的核心本质

《C和指针》这本书的核心,不是重新教你C语言语法,而是用指针的视角,重新理解C语言的所有核心特性,从内存底层搞懂C语言的运行逻辑,帮你从“会写语法”进阶到“懂C语言”。

ZhongWeiXiang9的博客 519

C语言指针与结构体:从 “(*ptr).” 到 “ptr->” 的2步语法糖解析与底层实现

本文深入解析C语言中指针与结构体的交互,重点探讨从`(*ptr).`到`ptr->`的语法糖转换及其底层实现。通过详细分析编译器处理过程、偏移量计算原理和不同编译器的实现差异,帮助开发者理解结构体成员访问的机制,避免常见陷阱,并提升代码效率与可读性。

weixin_34032792的博客 411

为什么C语言无法直接获取数组参数长度:底层原理+破解方法全曝光

掌握C语言数组参数的长度计算难题,解析其底层原理与实用破解方法。适用于函数传参场景,通过sizeof与宏定义等技巧实现精准计算,提升代码可靠性。方法简单高效,值得收藏。

GatherTide的博客 457
上一篇: C编译器剖析_1.5 结合C语言来学汇编_浮点数运算(比较大小)
下一篇: C编译器剖析_2.1 从Makefile走起
SheIsC
博客等级 码龄12年 224粉丝 64原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值