数组名为啥可以代表数组的首地址

在C语言中,数组名代表数组的首地址,这是由C语言的设计规范决定的。其核心原因可以归结为以下几点:

1. 数组名在大多数情况下会隐式转换为指针

C语言规定,数组名在表达式中使用时,会自动退化为指向数组首元素的指针(即 &array[0])。例如:

int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;  // 等价于 int *p = &arr[0];
  • 这里的 arr 会被编译器隐式转换为 &arr[0],即第一个元素的地址。

2. 数组名与指针的区别

虽然数组名可以当作指针使用,但它不是指针变量,而是地址常量,不能修改

  • 指针变量(如 int *p)可以修改其指向(p++)。
  • 数组名是固定地址,不能重新赋值(arr = new_arr; 会报错)。

示例

int arr[3] = {10, 20, 30};
int *p = arr;

p++;    // 合法,指针可以移动
arr++;  // 非法!数组名是常量,不能修改

3. 数组名代表首地址的底层逻辑

  • 内存布局:数组在内存中是连续存储的,数组名本质是编译器分配的内存块起始地址的符号
  • 效率优化:直接使用数组名作为指针避免了额外的地址计算,提升访问效率。

内存示例

地址     | 值
0x1000 | arr[0] (1)
0x1004 | arr[1] (2)
0x1008 | arr[2] (3)
  • arr 的值就是 0x1000(首元素地址)。

4. 例外情况:数组名不会退化为指针

在以下场景中,数组名不会退化为指针:

使用 sizeof(arr)
返回整个数组的字节大小(而非指针大小):

int arr[5];
printf("%zu", sizeof(arr)); // 输出 20(假设int为4字节)

使用 &arr
取数组地址时,得到的是指向整个数组的指针(类型为 int(*)[5]),而非首元素指针:

int arr[5];
int (*p)[5] = &arr; // p是指向含5个int的数组的指针, p+1跳转大小是5个int大小即20字节。

5. 为什么这样设计?

  1. 语法简洁性
    直接通过 arr[i] 访问元素,而不需显式写 *(arr + i)(虽然两者等价)。
  2. 兼容性
    C语言早期设计需要高效操作内存,数组名作为指针符合底层硬件访问模式。
  3. 历史原因
    继承自B语言(C的前身)的数组实现方式。

代码验证示例

#include <stdio.h>

// 数组为啥可以代表数组的首地址
int main()
{
    int array[5] = {10, 20, 30, 40, 50};
    int *p1 = array; // 数组名指向数组的首地址
    for (int i = 0; i < 5; i++)
    {
        printf("%d: %d --> %d\n", i, *(p1), *(array + i));
        p1++;
        // array++; //数组名是常量,不可修改
    }
    printf("\n");

    int arr[5] = {10, 20, 30, 40, 50};

    printf("arr     = %p\n", (void *)arr);     // 首元素地址
    printf("&arr[0] = %p\n", (void *)&arr[0]); // 同上
    printf("&arr    = %p\n", (void *)&arr);    // 整个数组的地址(值与arr相同,但类型不同)

    printf("sizeof(arr) = %zu\n", sizeof(arr));         // 输出12(整个数组大小)
    printf("sizeof(&arr[0]) = %zu\n", sizeof(&arr[0])); // 输出8(指针大小)

    int(*p)[5] = &arr; // p 指向5个int的数组的指针,p+1跳转大小是5个int大小即20字节。
    printf("p =%p\n", (void *)p);
    printf("p + 1 =%p\n", (void *)(p + 1));
    printf("diff (p + 1 - p) = %ld\n", (void *)(p + 1) - (void *)p);

    return 0;
}

输出(假设地址从 0x1000 开始):

0: 10 --> 10
1: 20 --> 20
2: 30 --> 30
3: 40 --> 40
4: 50 --> 50

arr     = 0x7ffd0d25eb10
&arr[0] = 0x7ffd0d25eb10
&arr    = 0x7ffd0d25eb10
sizeof(arr) = 20
sizeof(&arr[0]) = 8
p =0x7ffd0d25eb10
p + 1 =0x7ffd0d25eb24
diff (p + 1 - p) = 20

总结

行为

数组名 arr

指针 int *p

代表地址

首元素地址(&arr[0]

存储的地址值

能否修改

否(常量)

是(变量)

sizeof 结果

整个数组大小(字节)

指针大小(通常4或8字节)

& 操作的类型

int(*)[n](数组指针)

int**(指针的指针)

简单来说就是,数组名可以表示为指针,但它不是指针变量,而是地址常量,不能修改。

理解这一设计有助于避免以下常见错误:

  • 误将数组名当作指针变量修改(如 arr++)。
  • 混淆 arr&arr 的类型差异(尤其在函数传参时)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值