数组名与地址,函数名与地址有啥关系 个人总结

文章探讨了数组名和函数地址的本质,指出数组名代表数组本身而非指针,但在某些情况下会隐式转化为指针。函数名同样不等同于函数地址,但在表达式中可代表函数的入口地址。这种转化使得数组名在作为参数传递时失去常量和数组属性,而函数名则在内存中对应代码区的起始地址。

在网上查看了很多关于这方面的资料,关于这方面的说法很多,现在自己总结一下

————个人学习总结


数组名绝对不是指针,数组名指代一种数据结构,这种数据结构就是数组。说人话就是数组名其实就是指的数组本身,而之所以数组名绝大部分时候确实是代表数组首元素地址,其原因是内部发生了隐式转化(为什么会设置这种转化和怎样设置的知识水平有限,故不得而知),比如很多资料都说当数组作为函数参数时,退化为一个指针,这里的退化就是上面提到的隐式转化。而sizeof和&这两种特殊情况让数组名成为了数组本身,其原因就是其背后并没有这种隐式转换,并且数组名作为函数形参时,在函数体内,其失去了本身的内涵,仅仅只是一个指针,在失去其内涵的同时,它还失去了其常量特性,不能作自增、自减等操作,也不能被修改,如下:


关于函数地址:函数的定义只是一段代码,它描述了函数应该执行的操作。函数需要在程序中被调用时才会分配内存空间。实际上是保存在内存中的代码区(可以理解为程序 = 数据+代码,数据在堆栈中,而代码在代码区[会变成二进制的机器指令]),并将函数的机器指令(即函数的代码)存储到这个区域中。函数名称可以理解为这个代码区域的起始地址。当程序执行到函数调用语句时,会跳转到这个地址,从而执行函数的代码

函数名与地址的关系:函数名并不是函数地址的代表,函数名其实就是代表了函数本身,之所以函数名可以代表函数地址(准确来说时函数入口地址),也是因为会发生隐式转化让函数变成指针,就是说,这个转换在表达式中才会发生。比如在C语言中,一个函数占用一段连续的内存区,我们编写的程序,最终是不存在所谓的函数名的,有的只有内存单元地址如0xffeedd00.我们的程序执行时,程序加载到内存,比方说整个程序所占用的内存范围是0xffee0000 ~ 0xffeedd00
假设程序有一个小函数fun(),那么它的范围可能是0xffee0000~0xffee00ff。
那么函数fun()的入口地址是多少? 0xffee0000。那么函数名fun就是0xffee0000的别称(隐式转化)
用一个指针指向这个函数( 指针 = 函数名)实际就是把0xffee0000这个内存单元的地址给指针p。而函数名就是该函数所占内存区的首地址。(但是不能用函数名像数组名那样直接作为参数传递给另一个函数

<think>嗯,用户之前问过关于uint8二维数组中g[0]的指针行为问题,现在他又深入询问二维数组g[0]一维数组g的区别。看来用户正在学习C/C++中数组和指针的底层关系,特别是内存布局方面。从用户的问题描述看,他引用了站内几篇关于二维数组指针的重要资料,说明他已经做了功课。不过二维数组的指针概念确实容易混淆,尤其是数组名退化为指针时的微妙差异。用户可能正在编写需要直接操作内存的程序,比如图像处理或网络协议解析。这类场景下理解g[0]和g的确切区别至关重要。他特别提到“g[0]作为指针时的行为”,说明实际编码中遇到了指针运算问题。需要重点解释三个核心差异点:第一是数据类型本质,g是行指针而g[0]是元素指针;第二是sizeof操作结果差异;第三是指针运算时的步长区别。内存布局示意图会很有帮助,可以直观展示两者关系。注意到用户引用的资料[^3]中那个cout<< *(*(iArr+1)+1)的例子特别典型,正好能说明二维数组名和第一行指针的区别。类似示例应该纳入回答,但需要更详细拆解运算过程。用户可能是中级C++开发者,理解基本指针但需要深化多维数组知识。回答时要避免过多基础术语,着重实际内存操作场景。他看起来更喜欢技术细节而非抽象理论,之前回复中的指针运算字节偏移量分析就很对他胃口。</think> 在 C/C++ 中,二维数组 `g` 其第一行元素 `g[0]` 在类型、内存布局和指针行为上存在根本区别,理解这些区别对正确操作多维数组至关重要。 --- ### 一、核心区别分析 1. **数据类型维度** - **`g`(二维数组名)**: - **类型**:`uint8_t (*)[COLS]`(指向包含 `COLS` 个元素的数组的指针) - **本质**:行指针(Row Pointer),每次偏移移动**一整行** - **示例**: ```c uint8_t g[3][4]; // 3行4列数组 // g+1 的地址偏移 = sizeof(uint8_t) * 4 = 4字节 ``` - **`g[0]`(第一行首元素)**: - **类型**:`uint8_t*`(指向单字节数据的指针) - **本质**:列指针(Column Pointer),每次偏移移动**一个元素** - **示例**: ```c // g[0]+1 的地址偏移 = sizeof(uint8_t) = 1字节 ``` 2. **内存布局偏移计算** 假设 `g[3][4]` 起始地址为 `0x1000`: ``` 地址 | 元素 | 说明 ------------------------------- 0x1000 | g[0][0] | 0x1001 | g[0][1] | ← g[0]+1 指向此处 0x1002 | g[0][2] | 0x1003 | g[0][3] | ------------------------------- 0x1004 | g[1][0] | ← g+1 指向此处 (整行移动) 0x1005 | g[1][1] | ... | ... | ``` - **关键验证**: ```c printf("g地址: %p\n", (void*)g); // 输出 0x1000 printf("g+1地址: %p\n", (void*)(g+1)); // 输出 0x1004 (偏移4字节) printf("g[0]地址: %p\n", (void*)g[0]); // 输出 0x1000 printf("g[0]+1地址: %p\n", (void*)(g[0]+1)); // 输出 0x1001 (偏移1字节) ``` 3. **`sizeof` 操作的差异** | 表达式 | 结果 | 说明 | |----------------|---------------|--------------------------| | `sizeof(g)` | `3*4*1=12` | 整个二维数组的字节大小 | | `sizeof(g[0])` | `4*1=4` | 单行(包含4元素)的字节大小 | | `sizeof(g[0][0])` | `1` | 单个元素的字节大小 | --- ### 二、指针行为深度解析 1. **`g[0]` 作为指针的操作** - **元素访问等价性**: ```c g[0][2] ≡ *(g[0] + 2) // 访问第一行第三列元素 ``` - **跨行访问的危险操作**: ```c *(g[0] + 5) // 访问 g[1][1](跳过第一行) ``` *此操作破坏二维结构,易导致越界错误* 2. **二维数组名 `g` 的特性** - **行指针运算**: ```c uint8_t (*row_ptr)[4] = g; // 定义行指针 row_ptr++; // 移动到下一行起始位置 ``` - **跨行访问安全方式**: ```c g[1][1] ≡ *(*(g+1) + 1) // 通过行指针+列偏移 ``` --- ### 三、函数传参的关键区别 | 参数传递方式 | 函数声明示例 | 适用对象 | |------------------------|----------------------------------|----------------| | 一维指针 (`g[0]`) | `void func(uint8_t* ptr)` | 单行数据操作 | | 二维数组指针 (`g`) | `void func(uint8_t (*arr)[4])` | 整行操作 | | 动态传递 | `void func(uint8_t** ptr)` | 堆分配二维数组 | **重要限制**: 若使用 `g[0]` 传递整个二维数组: ```c // 错误!丢失列数信息,无法计算行偏移 void wrong_func(uint8_t* ptr) { ptr[5] = 10; // 实际访问 g[1][1],但无列数信息 } ``` **正确跨行传递方式**: ```c void correct_func(uint8_t arr[][4], int rows) { arr[1][1] = 10; // 明确列数=4 } ``` --- ### 四、内存布局对比图 ``` 二维数组 g[3][4] 一维数组等效视图 ┌───────────────┐ ┌───┬───┬───┬───┐ 行0 → │ g[0][0] │ 0x1000 │ 0 │ 1 │ 2 │ 3 │ ← g[0] 类型 uint8_t* ├───┬───┬───┬───┤ ├───┼───┼───┼───┤ 行1 → │ g[1][0] │ 0x1004 │ 4 │ 5 │ 6 │ 7 │ ├───┼───┼───┼───┤ ├───┼───┼───┼───┤ 行2 → │ g[2][0] │ 0x1008 │ 8 │ 9 │10 │11 │ └───┴───┴───┴───┘ └───┴───┴───┴───┘ g 类型: uint8_t(*)[4] 连续内存,无行列结构 g+1 → 0x1004 (偏移一行) ``` > **核心总结**: > 1. `g` 是**行指针**,`g[0]` 是**元素指针** > 2. `g+1` 移动**整行字节数**,`g[0]+1` 移动**单个元素字节数** > 3. 函数传递整数组必须保留列维度信息(如 `arr[][4]`) --- ### 五、典型应用场景 1. **单行处理(适合 `g[0]`)** ```c // 处理第一行数据 process_row(g[0], COLS); // 函数原型: void process_row(uint8_t*, int) ``` 2. **整数组操作(必须用 `g`)** ```c // 转置矩阵(需行列信息) transpose_matrix(g, ROWS, COLS); // 原型: void transpose(uint8_t (*mat)[COLS], int, int) ``` 3. **动态行访问** ```c for(int i=0; i<ROWS; i++) { uint8_t* current_row = g[i]; // g[i] ≡ *(g+i) modify_row(current_row, COLS); } ``` ---
评论 11
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kklovecode

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值