C编译器剖析_4.2 语义检查_表达式的语义检查(2)_数组索引

C编译器剖析_4.3 语义检查_语句statement 4.3  对语句Statement的语义检查    在这一节中,我们来分析一下对语句Statement的语义检查,与其相关的代码在stmtchk.c中。图4.3.1第1至16行的数组StmtCheckers,给出了用于对各语句进行语义检查的函数表,第17行的CheckStatement(stmt)函数根据参数stmt的kind域来查这个函数表,由函数指针来调用相应的函数完成语义检查,如图第18行 阅读详情

    在这一小节中,我们准备分析一下exprchk.c中的函数CheckPostfixExpression,此函数用于对后缀表达式进行语义检查。后缀表达式所对应的语法树请参照第3章的图3.1.21,这里不再重复。我们先从数组索引入手,图4.2.4给出了hello.c经UCC编译后生成的语法树hello.ast,中间代码hello.uil及最终生成的汇编代码hello.s的主要部分。


图4.2.4 数组索引ArrayIndex

    在图4.2.4第2行中,我们定义一个二维数组arr[3][4],第4行的ptr是一个指向int[4]数组的指针,而第5行的ptr1是一个指向int的指针,第6行的ptr2是一个指向int *的指针。第8至11行给出了对数组元素进行访问的一些后缀表达式。在UCC编译器内部构造的语法树形如第3章的图3.1.21,当然在语义检查时,我们会对语法分析时构造的语法树进行一些修改,为了便于调试分析,UCC编译器可以把语义检查后的语法树的主要部分打印出来,如上图hello.ast所示。例如,第9行的arr[1][2] = 2对应的抽象语法树AST为第21至24行,一对小括号表示了一棵AST子树,其中([]  arr  16)表示一棵以[]运算符为根,左操作数为arr,右操作数为16的语法树,我们不妨记这棵子树为arr[16];而([]  ([]  arr  16) 8)则表示了一棵以[]运算符为根,左操作数为子树arr[16],右操作数为8的抽象语法树,我们不妨把16和8加一起,记这棵子树为arr[24]。而(= ([]  ([]  arr 16)  8)  2)则表示一棵以赋值运算符=为根,左操作数为arr[24],右操作数为2的抽象语法树。为了可读性,UCC编译器在打印(= ([]  ([]  arr  16) 8)  2)时,增加了一些回车换行和缩进,如下所示。

         arr[1][2] = 2;

         //

(= ([] ([] arr

             16)

         8)

     2)

     稍微接触一下人工智能和LISP语言,我们就会发现图4.2.4中hello.ast的代码和LISP语言非常相似,实质上LISP语言就是一种直接建立在抽象语法树上的语言。换句话,LISP程序员编写的LISP代码,就是用字符串表示的抽象语法树。由图4.2.4第2行int arr[3][4]可知,arr是二维数组,由3个int[4]数组构成的数组,若每个int占4个字节,则每个int[4]数组要占用16个字节,因此在语义检查时,我们实际上把arr[1][2]转换成([] ([]  arr 1*16) 2*4),即([] ([]  arr 16)  8)。UCC源代码exprchk.c中的函数ScalePointerOffset(),会根据数组arr声明时的大小,把数组索引值1和2进行倍乘放大,进而得到16和8。当然如果索引值是变量,例如arr[x][y],则需要进行16*x+y*4的计算,才能得到arr[x][y]所在的内存单元。由于x和y的值没法在编译时确定,所以对16*x+y*4的求值,需要在运行时进行,编译时只能生成相应的代码,因此在语义检查时,我们需要为16*x这样的倍乘运算构造相应的语法树结点。函数ScalePointerOffset()的代码如图4.2.5所示。


图4.2.5 ScalePointerOffset()

     对于arr[x][y]而言,我们期望进行16*x的计算,则会调用ScalePointerOffset(x, 16),图4.2.5第65至72行构建了一个根结点为OP_MUL的语法树,第69行把x作为左操作数,而第72行则把倍乘系数16作为右操作数。当面对arr[1][2]时,相当于第60行的参数offset为编译时的常量1,此时我们完全可以在编译时进行1*16的乘法计算,没有必要把这个计算推迟到运行时,第74行的FoldConstant()函数完成了这个被称为“常量折叠”的操作。函数FoldConstant的代码在fold.c中,虽然较长,却很好理解,我们就不再啰嗦。

     由图4.2.4,我们可以发现,虽然在语法上第10和第11行的两条赋值语句几乎一模一样,但经语义检查后,生成的语法树却完全不同。其原因在于两者的寻址模式完全不一样,对于arr[0][0]而言,在汇编指令中,arr就是是数组的首地址,而arr+0就是元素arr[0][0]的地址,图4.2.3第51行的movl $3, arr+0实现了arr[0][0]=3的赋值功能。但对ptr2[0][0]而言,ptr2是个int **类型的指针,ptr2[0][0]的含义实际上是**ptr2,这需要进行两次的间接寻址,才能得到所要的内存单元。因此,在语义检查时,我们需要把ptr2[0][0]的语法树修改成形如(* (+  (* (+ ptr2 0))  0)),其中的子树(* (+ ptr2 0))完成了第一次间接寻址,此处的*表示的Dereference,而不是表示乘法运算,这个英文单词经常被译为“解引用”或者“提领”。第一次看到“提领”的翻译应是在侯捷先生的相关C++译作中,初看“提领”之词,似乎很唐突,但仔细品味,确实是再形象不过。相当于我们去逛商场时寄存物品,把私人物品存入寄存柜后,我们会得到一个小纸条,上面或者是箱号或者是一个条形码,之后我们凭着这个小纸条去“提取或领取”我们寄存的物品,而这个小纸条就相当于是C语言中的地址,所以“提领”之译实在是再妙不过。

         arr[0][0] = 3;   

         ptr2[0][0] = 5;

         //

(= ([]  ([]  arr  0)  0)  3)

(= (* (+  (* (+  ptr2  0))  0))  5)

    图4.2.4第8至11行中各语句对应的寻址模型如图4.2.6所示,我们可以发现,指针变量ptr2、ptr1和ptr都有相应的内存单元,数组arr的起始地址为0x804a070,而ptr1和ptr内存单元中的内容都是0x804a070,这意味着这两个指针都指向数组arr的开始位置。并且,arr[0][0]和ptr2[0][0]对应相同的内存单元,ptr[1][2]和arr[1][2]对应相同的内存单元。要通过指针ptr定位到ptr[1][2],我们需要先计算出0x804a070+1*16+2*4,即0x804a070+24,然后再由地址进行“提领”运算即可,相应的中间代码如图4.2.4第37和38行所示,而汇编代码如第47至49行所示。而要由ptr2定位到ptr2[0][0],则需要经过ptr2+0*4的运算,此处0*4中的4来源于sizeof(int *)为4,编译器进行常量折叠后,即为ptr2,然后进行*(ptr2)的提领操作,即得到ptr1中的内容0x804a070,再进行0x804a070+0*4的运算,其中0*4中的4是由于sizeof(int)为4,之后再根据常量折叠后的0x804a070进行提领操作。与之对应的中间代码如图4.2.4第41至42行所示,而汇编代码如图第52至54行所示。而arr[0][0]和arr[1][2]的定位则不需要在运行时进行“提领”操作,要访问的内存单元的首地址在编译时就可以计算出来,即arr和arr+1*16+8(即arr+24),对应的中间代码如图第39和40行所示,汇编代码如图50和51行所示。


图4.2.6 数组寻址

    我们会在后续章节讨论中间代码和汇编代码的生成,这里把相应的中间代码和汇编代码放在一起讨论的目的是为了进一步熟悉这些概念。对arr[0][0]和ptr[0][0]而言,两者在语法分析后对应的语法树是同构的,但由上述分析可见,在语义检查后,两者的语法树有较大差别,引起这种区别的源头在于arr和ptr2具有不一样的类型,在对arr和ptr2的声明进行语义检查后,arr是int[3][4]类型,而ptr2是int **类型,我们会把它们的类型信息记录到符号表中。经过这些准备工作后,现在让我们来看看对数组索引的语义检查的相关代码,如图4.2.7所示。图中第4至31行完成了对数组索引OP_INDEX运算符表达式的语义检查,而第32至33行则调用CheckFunctionCall(expr)来完成对函数调用OP_CALL的语义检查。而“后加加”与“后减减”则通过调用TransformIncrement(expr)来检查,成员选择运算符OP_MEMBER和OP_PTR_MEMBER(即.和->运算符)则通过CheckMemberAccess(expr)来处理。我们会在稍后对这几个函数进行分析。


图4.2.7 CheckPostfixExpression()

     对于arr[1][2]而言,我们先来分析arr[1],如果把[]看成是运算符,则该后缀运算符实际上也有两个操作数,一个是arr,另一个是1。我们需要在图4.2.7第5和第6行先对[]运算符的左右子树进行语义检查,这通过递归地调用CheckExpression()来完成,此处,左操作数arr和右操作数1实际上已是一个基本表达式PrimaryExpression,真正被调用的函数是CheckPrimaryExpression()函数,我们在稍后会对这个函数进行分析。在此函数中,会查符号表,得到arr的类型为int [3][4]。此时,按照C的语义,arr对应的语法树结点的类型需要被调整为指向数组元素的指针类型,对数组类型int [3][4]而言,其数组元素为int[4]为类型,所以arr对应的语法树结点的类型为int (*)[4]。函数Adjust()完成了这个类型调整的工作,我们已经在上一小节中讨论过函数Adjust()。令大多数C程序员大跌眼镜的是” 1[arr][2] = 1;”竟然也是合法的C语句,其等效于”arr[1][2] = 1;”,实际开发中,恐怕只有孔乙已穿越到现代才能写出如此奇葩的C语句来。不过C编译器还是要支持之,第7至9行用于把1[arr]转换成arr[1]来处理。坦白讲,实在看不出C编译器支持如此语法有何意义,权当满足可交换性和孔乙已对茴香豆的“茴”有4种写法的特殊癖好吧。当[]的左操作数是指向对象的指针类型(即除了函数指针外的指针类型),而右操作数是整数类型,则第10行的条件成立,我们就可进入第11行进行处理;否则跳到第31行进行错误处理,此时[]的左右操作数的类型不正确。这里,我们能看到类型系统再次派上用场。因为arr对应结点的类型已经被调整数int (*)[4],同时该结点的isarray域也会在Adjust()函数中被置为1,代表arr对应的语法树结点的原有类型为数组类型。第11行把[]对应的语法树结点的类型置为int[4],而第13行则把右操作数1进行整数提升,即把short或char类型提升为int类型,第14行则调用我们前面讨论过的ScalePointerOffset()函数来实现1*16的运算,因为sizeof(int[4])正好就是16。因为arr对应的结点的isarray域被置为1,且[]对应的语法树结点的类型int[4]也是数组类型,所以第15行的条件不成立,此时我们直接从第29行返回。这样,我们就完成了对arr[1]的语义检查,我们会返回如下语法树,其根结点的类型为int[4],即arr[1]的类型为int[4]。

         ([]  arr  16)

         按照后根遍历的次序,接下来我们会再对arr[1][2]进行检查,此时左子树为arr[1],而右子树为2,类似的,我们会得到以下语法树,其根结点的类型为int。第12行会标记该语法树根结点为左值,此处即arr[1][2]可充当左值,故arr[1][2]=1是合法的。当然,按照图4.2.7的代码,当检查语句“arr[1] = 6;”时,我们会在第12行把arr[1]对应的结点的lvalue域也误设为1,但因为arr[1]的类型为数组类型int[4],在检查赋值运算符=时,即后续要分析的函数CheckAssignmentExpression中,我们会通过Adjust()函数,再次把arr[1]结点对应的lvalue置为0,即arr[1]不可充当左值,所以会报错“error:Theleft operand cannot be modified”。

         ([]  ([] arr  16)  8)

         同理,对于ptr[1][2]和arr[0][0],图4.2.7第15行的条件都不会成立,所以我们分别得到以下语法树。

         ([]  ([]  ptr 16) 8)

         ([]  ([] arr  0)  0)

         但是,对ptr2[0][0]来说,图4.2.7第15行的条件就会成立。我们先来看ptr2[0],查符号表可得ptr2原来的类型就是int **,而ptr2[0]对应的语法树结点的类型就应是int*,即ptr2不是数组类型且ptr2[0]也不是数组类型,所以第15行的条件会成立。第16至27行的代码则用于构造语法树(* (+  ptr2  0) ) 。按后根遍历的次序,完成对ptr2[0]的检查后,我们会再对ptr2[0][0]进行检查,此时ptr2[0]为左子树,而0为右子树,同理可得到如下语法树。

         (* (+  (* (+ ptr2  0))  0))

         ptr2[0][0]仍然可访问到数组元素arr[0][0],但如果想通过ptr2[1][2]来访问arr[1][2]则就是大错特错。因为ptr2[1][2]在语义检查后,对应的语法树为:

         (* (+  (*  (+  ptr2  4) )  8))

         按图4.2.6所示,ptr2的内容为0x804a54,所以ptr2+4的结果为0x804a58,之后根据地址0x804a58去做“提领”操作,天知道该单元中存放的是什么内容。God Bless You.

C编译器剖析_3.1 语法分析_C语言的表达式(1) 3.1  C语言的表达式     从第3章开始,我们进入UCC编译器的语法分析阶段。相关的代码主要在ucl\decl.c、ucl\expr.c和ucl\stmt.c,分别对应声明Declaration、表达式Expression和语句Statement。在第一章时,我们已经通过一个简单的例子ucc\examples\sc对这些概念有了一个直观的体会,在这一章中,我们需要结合IT大佬们制定的C标准 阅读详情

相关推荐

GNU C与ANSI C核心差异及Linux开发实践

C语言作为系统编程的核心语言,其标准实现存在多个分支。ANSI C作为标准化版本,提供了跨平台的基础编程规范,而GNU C在此基础上进行了针对Linux系统开发的深度扩展。从编译器原理角度看,GNU C通过语法扩展、内建函数和属性声明等机制,显著增强了底层硬件控制和系统级编程能力。这些特性在Linux内核开发中体现尤为突出,如零长度数组优化内存布局、__builtin函数提升性能预测准确性等。通过case范围表达式、语句表达式等语法糖,GNU C在保持C语言高效特性的同时,大幅提升了代码可读性和安全性。对于

weixin_42594427的博客 365

各种遇到的异常以及异常的处理

1. java.lang.UnsupportedOperationException: A TupleBackedMap cannot be modified. 报错类似的代码: List<HashMap<String, Object>> newMapList = new ArrayList<>(); for (int i = 0; i < mapList...

胃病的不良少彦 6630

深入解析C/C++编译器错误:从指针到模板的编程核心原理

在C/C++编程中,编译器错误信息是理解语言底层机制的重要窗口。类型系统和内存模型构成了程序安全性的基础,编译器通过严格的类型检查确保操作符与操作数的匹配,防止未定义行为。指针作为直接操作内存的工具,其地址合法性、类型匹配和边界检查是避免程序崩溃的关键。模板作为泛型编程的核心,通过编译时多态实现代码复用,但参数推导和实例化规则需要精确掌握。这些原理在高性能计算和嵌入式系统等场景中尤为重要,能帮助开发者写出更健壮的代码。本文以C1833、C1854等典型错误为例,系统剖析从指针操作到模板实例化的核心思想,提升

weixin_30787531的博客 305

C编译器剖析_4.2 语义检查_表达式的语义检查(1)

在这一节中,我们来分析一下exprchk.c中的代码。我们通过调用图4.2.1中的函数CheckExpression(),来对表达式所对应的语法树结点进行语义检查。虽然表达式结点对应的kind域皆为NK_Expression,但它们的运算符op域还是各有区别的,由此我们可以为不同的运算符表达式构造不同的语义检查函数,然后把这些函数放置在一张函数表ExprCheckes中,用运算符op作为下标,去调

SheIsC的专栏 1697

嵌入式C语言数组原理与实战:从内存布局到环形缓冲区

数组是C语言中对连续内存块进行逻辑分组的基础数据结构,其本质是编译期确定大小、运行时通过基址+偏移实现O(1)索引访问的线性存储模型。在嵌入式系统中,数组直接映射硬件线性地址空间,支撑传感器采样、通信协议帧、状态机跳转表等关键模块。掌握数组名作为常量指针、`sizeof`语义差异、边界安全访问及`const`/`volatile`修饰规则,是实现确定性、可维护固件的前提。本文结合STM32裸机开发场景,深入剖析一维/二维数组内存布局、字符数组与字符串区别、查找表(LUT)优化、函数指针跳转表及无锁环形缓冲区

weixin_36078669的博客 31

Penpot自托管全攻略:5分钟搞定私有化部署(附Docker配置详解)

本文详细介绍了Penpot开源原型设计工具的私有化部署全流程,从Docker基础配置到企业级优化方案。通过分步指南和实战技巧,帮助设计团队快速搭建安全高效的自托管环境,实现数据自主可控。内容涵盖LDAP集成、性能监控、备份策略等关键企业需求,是Penpot私有化部署的完整参考手册。

sugar的博客 1132

构建自己的编译器(九)实质性的第一步

我现在用flex+bison实现的,昨天看了一晚上。。。 现在实现了定义,真的很不错了。 在lexer.l中,标识符的正则表达式是: [a-zA-Z][a-zA-Z0-9_]* { yylval.strval = strdup(yytext); return IDENTITY; } 然后需要一个赋值符: ":" { return ASS...

HeroIsUseless的博客 269

别再对着手册发愁了!手把手教你读懂MIPI-DSI初始化命令(以Rockchip平台为例)

本文深入解析MIPI-DSI初始化命令在Rockchip平台的应用,帮助嵌入式工程师快速掌握LCD屏幕调试技巧。从协议基础到实战指南,详细拆解命令格式、DCS与Generic命令的区别,并提供Rockchip平台的调试全流程与高级技巧,解决常见显示问题。

weixin_42519781的博客 262

Java向量API不生效?——揭秘C2编译器向量化失败的6种隐藏条件(附JITWatch诊断模板)

揭秘Java向量API性能不达预期的根因,直击C2编译器向量化失败的6类隐藏条件(如循环结构、数据对齐、类型混合等)。提供JITWatch诊断模板与优化对照表,助你精准定位并提升向量化成功率。Java向量API性能调优实战指南,值得收藏。

CodeNexus的博客 182

手把手解决GD32H759I-EVAL以太网通信三大坑:IP冲突、端口绑定失败、LWIP内存泄漏

本文针对GD32H759I-EVAL开发板在以太网通信调试中常见的三大难题——IP地址冲突、端口绑定失败和LWIP内存泄漏,提供了详细的实战排障指南。文章深入分析了问题根源,并给出了具体的网络配置、端口管理策略和内存泄漏检测方法,帮助开发者快速定位并解决嵌入式网络通信中的棘手问题,确保系统稳定运行。

weixin_29196315的博客 139

【数据分析/商业分析】面试题整理——SQL专题

数据分析\商业分析—面试题整理 自己总结的SQL类的面试题 文章目录数据分析\商业分析—面试题整理1.新增用户2.活跃用户数量3.连续登录4.次(n)日留存5.每个科目下分数最高的两名学生

awater_17的博客 1858

C语言基础-五、数组

本文系统介绍了C语言中数组的核心概念与应用。主要内容包括:1.数组基础:连续内存存储、类型统一、固定长度等特性;2.数组操作:定义初始化、元素访问、遍历及地址计算;3.关键问题:数组越界、栈空间限制、函数参数退化;4.常用算法:查找(顺序/二分)、排序(冒泡/选择)、统计计算;5.高级应用:二维数组内存布局、动态内存分配与管理。文章通过大量代码示例,详细讲解了数组的底层原理和实际应用技巧,并提供了常见错误解决方案和性能优化建议。

aixuexi 1069

深入解析组合逻辑环(combinational loop)的成因与解决方案

本文深入解析了数字电路设计中组合逻辑环(combinational loop)的成因与危害。通过边沿检测和中断轮询(round_robin)仲裁器等实例,揭示了代码如何意外形成环路,并对比了插入触发器、重构逻辑以及状态机思维三种核心解决方案,帮助硬件工程师从根本上避免这一常见设计陷阱,确保电路稳定可靠。

weixin_28278377的博客 313

Cfront源码解析:从C++到C的翻译机制与编译器设计启示

编译器作为编程语言的核心工具,其工作原理是将高级语言代码转换为机器可执行的指令。源码到源码翻译是一种经典的编译器实现方式,它通过将一种高级语言转换为另一种更基础的语言来实现跨平台和快速原型验证。这种技术不仅揭示了语言特性的底层实现逻辑,还为开发者提供了深入理解运行时机制和内存模型的窗口。在工程实践中,掌握编译器前端技术能够赋能开发者进行代码分析、性能优化和领域特定语言设计。Cfront作为最早的C++到C源码转换器,通过将类、继承、虚函数等面向对象概念映射为C语言的结构体和函数指针,清晰地展示了C++对象模

weixin_33690963的博客 420

流形优化: Manifold Optimization 的 全网最通俗版本详解 (一)

前言 之前忙于论文, 停更了很久。 接下来的一段时间,出于各种原因, 决定在优快云平台上做 最近在 混合波束成形 及 智能反射面 领域 非常常见的流形优化 (Manifold Optimization) 算法 的专题介绍。尽管目前已有了不少的中文博客, 但出于其各种弊端, 使得本篇的成文仍有其不可或缺的意义。 而如果要盘点最核心的意义, 就在于这篇是对读者的 数学能力 与 阅读理解能力 要求最低的 流形优化讲解了。 唯一参考的文献为 Manopt 工具包作者 Boumal 的 新书: An introd

B417科研笔记 2万+

ANSYS——模态分析的理论基础

ANSYS模态分析理论基础

python_AI_fans的博客 9075

【C语言高阶编程秘籍】:突破函数返回数组限制的底层原理剖析

掌握C语言中函数返回数组的方法,解决无法直接返回数组的难题。详解指针、动态分配与结构体封装等核心技巧,适用于嵌入式开发与算法实现,提升代码效率与灵活性。值得收藏

ByteVein的博客 976

全国区块链职业技能大赛国赛考题前端功能开发

全国区块链职业技能大赛国赛考题区块链应用前端功能开发

本郡主是喵 1333

C语言数组长度传参为何总是出错?(十年经验总结的黄金法则)

掌握C语言数组参数的长度计算关键技巧,避免常见传参错误。适用于函数间数组传递场景,通过传参时显式传递长度或使用宏定义实现精准计算,提升代码稳定性与可维护性。十年开发经验总结,值得收藏。

QuickSolve的博客 462

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

【C语言数组传参长度难题】:揭秘函数中获取数组真实长度的5种高效方法

解决C语言数组传参长度难题,揭秘5种高效获取真实长度的方法。适用于函数参数传递场景,涵盖宏定义、指针运算等技巧,保持代码高效可读。掌握这些方法,轻松应对数组处理挑战,值得收藏。

codeink的博客 912

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

基于Rust与多智能体架构的C语言编译器设计与实现

编译器作为将高级语言转换为机器可执行代码的核心系统软件,其设计通常遵循词法分析、语法分析、语义分析、中间代码生成与优化、目标代码生成等经典阶段。传统编译器多为单线程顺序执行,而现代软件架构正朝着模块化、并发化方向发展。多智能体系统通过将复杂任务分解为多个可并行、可通信的专长模块,为系统设计提供了新的范式。结合Rust语言的内存安全、零成本抽象和高并发特性,能够高效构建高可靠性的系统级软件。本文聚焦于将多智能体协同架构应用于编译器构建这一具体场景,详细阐述了如何利用Rust的异步编程模型(如tokio运行时与

weixin_33778544的博客 496

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

C/C++数组与指针的本质区别及内存访问机制

在C/C++编程中,数组和指针是处理内存数据的核心概念。数组作为连续内存块的抽象,通过下标访问实现高效元素定位,其本质是编译器将arr[i]转换为*(arr + i)的指针运算。指针则提供更灵活的内存操作能力,但需要开发者手动管理地址计算。这种差异在内存访问机制上尤为明显——数组访问隐含边界检查,而指针运算可能引发越界风险。理解二者的类型系统差异(如&arr与&arr[0]的类型不同)对避免内存错误至关重要。在实际工程中,数组常用于固定大小数据集合(如传感器数据缓存),指针则更适合动态数据结构(如链表实现)

weixin_30655569的博客 383

C语言数组传参详解:从指针退化到内存模型解析

在C语言编程中,理解数组与指针的关系是掌握内存操作的关键基础。数组名在大多数表达式中会自动退化为指向其首元素的指针,这一特性深刻影响了函数参数传递的机制。从原理上看,这种设计使得C语言能够高效地操作内存,但同时也带来了数组大小信息丢失的挑战。其技术价值在于提供了灵活的内存访问方式,是构建高性能系统和底层库的基石。在实际应用场景中,无论是处理一维数据集合还是实现多维矩阵运算,都需要明确传递数组长度或使用哨兵值来界定数据边界。本文聚焦于数组传参这一核心主题,通过剖析指针退化现象和内存模型,帮助开发者规避常见的s

weixin_30621959的博客 395

C语言最常见的线性结构---数组

C语言最常见的线性结构—数组 数组详解 数组定义:有序数据的集合,用数组名作为标识 数组性质:数组是由一些列相同数据类型组成的线性集合 数组组成:标识符、类型名、维数。其中,维数指定数组中的元素个数。 数组优缺点: 1)与vector类型相比,数组缺陷在于:数组的长度是固定的,不能知晓数组的长度。同时没有size和push_back操作。如果要更改长度,则要创建更大的新数组,再复制原数组的所有元素。 注:vector是C++中的STL容器的一个容器分支,属于动态数组内容 2)数组是被严格限制

Nwk0000000001的博客 1176

3.数组和指针【由浅入深.c】

本文介绍数组和指针相关内容。(【由浅入深】是一个系列文章,它记录了我个人作为一个小白,在学习c++技术开发方向计相关知识过程中的笔记,欢迎各位彭于晏刘亦菲从中指出我的错误并且与我共同学习进步,作为该系列的第一部曲-c语言,大部分知识会根据本人所学和我的助手——通义,DeepSeek,腾讯元宝等以及合并网络上所找到的相关资料进行核实誊抄,每一篇文章都可能会因为一些错误在后续时间增删改查,因为该系列按照我的网络课程学习笔记形式编写,我会使用绝大多数人使用的讲解顺序编写,所以基础框架和大部分内容案例会与他人一样,

插花弄玉的博客 1132

C/C++数组与指针深度解析:从内存模型到多维访问实战

在C/C++编程中,数组和指针是理解内存模型与数据访问的核心基础。数组在内存中连续存储,其名称在大多数表达式中会隐式转换为指向首元素的指针常量,这一特性直接映射到底层的内存寻址操作。理解指针运算和解引用原理,是掌握高效、安全内存操作的关键,尤其在处理动态内存、函数参数传递及复杂数据结构时至关重要。本文以多维数组为例,深入剖析了如`*a`、`*a[0]`、`*(*(a+i)+j)`等表达式的本质,通过内存布局图示和代码示例,揭示其与`a[i][j]`的等价关系,并探讨了指针数组、数组指针以及`sizeof`运

weixin_34414196的博客 505
上一篇: C编译器剖析_4.2 语义检查_表达式的语义检查(1)
下一篇: C编译器剖析_4.2 语义检查_表达式的语义检查(3)_字符串与标识符
SheIsC
博客等级 码龄12年 224粉丝 64原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值