数组名是什么?

在C语言中,‌数组名是标识符‌,其本质是一个‌用户自定义的标识符‌,用于标识整个数组对象。它本身‌不是指针‌,但在大多数表达式上下文中会“退化”为指向数组首元素的指针。


数组名作为标识符的存放位置

  • ‌数组名本身(标识符)‌:
    它是‌编译时的概念‌,用于在源代码中引用数组。‌标识符并不占用运行时内存‌,它在编译阶段被符号表记录,链接时用于定位数组的内存地址。

  • ‌数组所占内存空间‌:
    数组的实际数据存储在‌内存的某个区域‌(如栈、堆或全局/静态区),具体取决于数组的‌定义位置‌和‌存储类别‌:

    • ‌局部数组‌(函数内定义):存储在‌栈区‌。
    • ‌全局/静态数组‌:存储在‌全局/静态存储区‌。
    • ‌动态分配数组‌(如通过 malloc):存储在‌堆区‌。

⚠️ 注意:数组名‌不是变量‌,不能被赋值(如 a++ 是非法的),因为它代表的是一个‌常量地址‌(即数组首元素的地址)‌

总结

  • ‌数组名是标识符‌,用于在代码中引用数组;
  • ‌标识符不“放在”内存中‌,它是编译器符号表中的条目;
  • ‌数组数据‌存储在栈、堆或全局区,取决于定义位置;
  • 数组名在表达式中通常‌退化为指向首元素的指针‌,但‌本质不是指针‌‌,见下。

在 C 语言中,‌数组名是标识符‌,会被编译器记录在‌符号表(Symbol Table)‌中。符号表是编译过程中的一个核心数据结构,用于管理和存储源程序中所有标识符(如变量、函数、数组等)的语义信息。

数组名与指针的本质差异
在C语言开发中,数组名和指针的混淆堪称经典误区。我曾在一个嵌入式项目中,因为错误地将数组名当作指针处理,导致系统出现难以追踪的内存异常。经过调试分析,才发现问题根源在于对二者本质理解不足。

数组名在编译期就被确定为固定地址常量,这个特性直接影响编译器的处理方式。当编译器遇到 int arr[10] 这样的定义时,会在符号表中记录:

标识符类型:数组
元素类型:int
元素个数:10
起始地址:0xXXXX(编译时确定)
而指针变量 int *ptr 的符号表记录则是:

标识符类型:指针变量
指向类型:int
存储地址:运行时确定
关键区别:数组名是地址标签,指针是地址容器。就像门牌号和信箱的关系——门牌号直接标识位置,而信箱可以存放不同地点的门牌号。
数组名存放在符号表中,包含多条信息。指针存放在栈中,仅包含地址,及偏移量(根据指针类型)

  • 数组名、函数名,全局变量,静态变量都会生成符号。局部变量不会生成符号表,在栈或堆中,不会在函数外部 被引用。

‌符号表是什么?‌

‌符号表‌是编译器在编译过程中用来‌记录源程序中各个符号(标识符)的必要信息‌的数据结构,主要用于:

  • 辅助‌语义正确性检查‌
  • 支持‌中间代码生成‌
  • 进行‌地址分配‌和‌符号解析‌

它以标识符名称为关键字,存储每个符号的多种属性信息 ‌13。


‌符号表中记录的典型信息‌

对于一个数组名(如 int arr‌:ml-citation{ref="10" data="citationList"};),符号表中通常包含以下内容:

  • ‌名称(Name)‌:标识符名,例如 "arr"
  • ‌类型(Type)‌:数据类型,例如 int‌:ml-citation{ref="10" data="citationList"}(整型数组,长度为 10)
  • ‌种类(Kind)‌:如 数组变量函数 等
  • ‌作用域(Scope)‌:全局或局部(如函数内定义)
  • ‌存储位置(Address)‌:编译时确定的起始地址(如 0x1000
  • ‌扩展属性‌:对数组而言,包括‌维度‌和‌各维度长度‌ ‌210

示例:
int arr‌:ml-citation{ref="10" data="citationList"}; 在符号表中的条目可能如下:

  • 名称:arr
  • 类型:int
  • 维度:1
  • 长度:10
  • 起始地址:0x1000
  • 类别:数组 ‌2

在C语言中,‌数组名作为标识符确实会被记录在符号表中‌,但符号表本身是‌编译器在编译阶段使用的内部数据结构‌,并不直接对应程序运行时的内存布局。

符号表的位置

  • ‌符号表存储在编译器进程的内存中‌,属于编译器的内部工作空间,‌不属于目标程序或运行时程序的内存区域‌(如代码段、数据段、堆、栈等)‌
  • 编译完成后,符号表通常‌不会被包含在最终的可执行文件中‌(除非保留调试信息,如通过 -g 编译选项生成 .debug_info 等段)‌

数组名在符号表中的记录内容

对于数组名(如 int arr‌:ml-citation{ref="10" data="citationList"};),符号表条目通常包含以下信息‌23:

  • ‌名称(Name)‌:arr
  • ‌类型(Type)‌:数组类型(如 int‌:ml-citation{ref="10" data="citationList"}
  • ‌作用域(Scope)‌:全局或局部
  • ‌存储位置(Storage Location)‌:‌数组首元素的地址‌(编译时确定的绝对地址或相对偏移)

⚠️ 注意:数组名‌本身不占用运行时内存空间‌,它只是一个“地址常量”,其值直接绑定到数组首元素的地址。这与指针变量不同,指针变量会占用4字节(32位)或8字节(64位)的运行时内存来存储地址值‌。同时,两者包含的信息也不一样。数组名不是常指针变量。

总结

  • ‌符号表位于编译器进程的内存中‌,是编译阶段的临时结构。
  • ‌数组名的地址信息(即数组首地址)在链接后被固化为程序数据段中的一个固定偏移‌,属于运行时内存布局的一部分。
  • 程序运行时,‌没有独立的“符号表”内存区域‌;符号表仅用于编译和链接阶段。

如需查看编译后的符号信息,可使用 nm 或 objdump -t 等工具分析目标文件或可执行文件中的符号表(若未被剥离)‌

‌为什么数组名要放在符号表?‌

因为数组名在编译期就被视为一个‌固定地址的常量标识符‌,不同于指针变量。编译器需要通过符号表来:

    • 确认数组名的类型和大小
    • 正确计算元素偏移(如 arr[i] 的地址 = arr起始地址 + i * sizeof(int)
    • 区分数组名与指针变量(后者存储的是可变地址)‌

‌补充说明‌

  • 符号表不仅用于编译阶段,‌链接阶段‌也会使用目标文件中的符号表来解析外部函数或全局变量的引用 ‌49。
  • 实际编译器中,符号表常采用‌哈希表‌实现,以支持快速插入和查找
posted @ 2026-04-09 22:52  师大无雨  阅读(4)  评论(0)    收藏  举报