小熊奶糖(BearCandy)
小熊奶糖(BearCandy)
发布于 2025-11-05 / 7 阅读
0
0

C语言数组与sizeof操作符详解

C语言数组与sizeof操作符详解

核心概念:数组名的"退化"规则

基本规则

  • 数组名在大多数表达式中会自动转换为指向其第一个元素的指针
  • 例外情况(数组名不退化为指针):
    1. 使用 sizeof 运算符时
    2. 使用取地址运算符 &

sizeof不同用法的结果对比

示例数组定义

int arr[20];  // 包含20个整数的数组
// 假设:sizeof(int) = 4字节,指针大小为8字节(64位系统)

详细对比表

表达式 arr的类型 sizeof结果 解释说明
sizeof arr int [20] 80字节 计算整个数组的大小:20 × 4 = 80字节
sizeof (arr + 1) int* 8字节 计算指针的大小,arr退化为指针
sizeof arr[0] int 4字节 计算数组单个元素的大小
sizeof &arr int (*)[20] 8字节 计算数组指针的大小
sizeof *arr int 4字节 arr退化为指针,解引用得到int

验证代码示例

#include <stdio.h>

int main() {
    int arr[20];
  
    printf("=== sizeof 结果对比 ===\n");
    printf("sizeof arr          = %2zu (整个数组大小)\n", sizeof arr);
    printf("sizeof (arr + 1)    = %2zu (指针大小)\n", sizeof(arr + 1));
    printf("sizeof arr[0]       = %2zu (int大小)\n", sizeof arr[0]);
    printf("sizeof &arr         = %2zu (指针大小)\n", sizeof &arr);
  
    printf("\n=== 地址验证 ===\n");
    printf("arr      = %p\n", (void*)arr);
    printf("arr + 1  = %p (前进4字节,一个int大小)\n", (void*)(arr + 1));
    printf("&arr     = %p (与arr值相同)\n", (void*)&arr);
    printf("&arr + 1 = %p (前进80字节,整个数组大小)\n", (void*)(&arr + 1));
  
    return 0;
}

关键记忆点

1. 数组名在不同上下文中的含义

arr       // 在sizeof中:整个数组
          // 在其他表达式中:指向第一个元素的指针

arr + 1   // 总是:指向第二个元素的指针
&arr      // 总是:指向整个数组的指针

2. 指针运算的差异

arr + 1   // 地址增加 sizeof(int) = 4字节
&arr + 1  // 地址增加 sizeof(arr) = 80字节

3. 实用判断方法

  • 如果 sizeof直接作用于数组名 → 得到整个数组大小
  • 如果数组名出现在表达式中 → 通常退化为指针
  • &运算符作用于数组名 → 得到指向数组的指针

常见误区提醒

错误理解:认为 arr总是指针
正确理解arr是数组,在特定情况下退化为指针

错误理解sizeof(arr+1)计算第二个元素的大小
正确理解sizeof(arr+1)计算指针本身的大小

这个笔记总结了数组与sizeof的核心知识点,建议结合代码实践来加深理解。

C语言数组的退化机制详解

什么是数组退化?

在C语言中,数组退化(Array Decay)是指数组在特定情况下会自动转换为指向其首元素的指针。这是C语言中一个重要且容易混淆的特性。

数组退化的发生场景

  1. 函数参数传递

当数组作为函数参数传递时,会发生退化:

#include <stdio.h>

// 函数声明中的数组参数实际上是指针
void printArray(int arr[], int size) {
    // 这里的arr实际上是指向int的指针
    printf("Sizeof arr in function: %zu bytes\n", sizeof(arr)); // 输出指针大小
    for(int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main() {
    int myArray[5] = {1, 2, 3, 4, 5};
  
    printf("Sizeof myArray in main: %zu bytes\n", sizeof(myArray)); // 输出20字节
  
    // 传递数组给函数时发生退化
    printArray(myArray, 5);
  
    return 0;
}

输出:

Sizeof myArray in main: 20 bytes
Sizeof arr in function: 8 bytes
1 2 3 4 5
  1. 数组赋值给指针变量
int main() {
    int arr[5] = {1, 2, 3, 4, 5};
  
    // 数组退化发生:arr退化为指向首元素的指针
    int *ptr = arr;
  
    // 以下两种访问方式是等价的
    printf("arr[2] = %d\n", arr[2]);    // 输出:3
    printf("ptr[2] = %d\n", ptr[2]);    // 输出:3
    printf("*(arr+2) = %d\n", *(arr+2)); // 输出:3
  
    return 0;
}
  1. 数组在表达式中的使用
int main() {
    int arr[3] = {10, 20, 30};
  
    // 在表达式中,数组名退化为指针
    int *p1 = arr + 1;  // 指向第二个元素
    int *p2 = &arr[0] + 1; // 等价写法
  
    printf("*p1 = %d\n", *p1); // 输出:20
    printf("*p2 = %d\n", *p2); // 输出:20
  
    return 0;
}

不会发生退化的场景

  1. sizeof运算符
int main() {
    int arr[5] = {1, 2, 3, 4, 5};
  
    // sizeof不会导致数组退化
    printf("Size of array: %zu\n", sizeof(arr)); // 输出:20(5 * 4)
  
    // 与指针大小对比
    int *ptr = arr;
    printf("Size of pointer: %zu\n", sizeof(ptr)); // 输出:8(64位系统)
  
    return 0;
}
  1. &运算符
int main() {
    int arr[5] = {1, 2, 3, 4, 5};
  
    // &arr不会退化,得到的是指向整个数组的指针
    int (*ptr_to_array)[5] = &arr;
  
    printf("Address of arr: %p\n", (void*)arr);
    printf("Address of &arr: %p\n", (void*)&arr);
    // 两个地址值相同,但类型不同
  
    // 指针运算的差异
    printf("arr + 1: %p\n", (void*)(arr + 1));     // 前进4字节
    printf("&arr + 1: %p\n", (void*)(&arr + 1));   // 前进20字节
  
    return 0;
}
  1. 字符串字面量的初始化
int main() {
    // 字符串字面量初始化字符数组时不会退化
    char str[] = "Hello"; // 创建了一个字符数组
  
    // 这与指针不同
    char *ptr = "Hello";  // 指针指向字符串字面量
  
    printf("Sizeof str: %zu\n", sizeof(str)); // 输出:6(包含'\0')
    printf("Sizeof ptr: %zu\n", sizeof(ptr)); // 输出:8
  
    return 0;
}

多维数组的退化

多维数组的退化更为复杂:

#include <stdio.h>

void print2DArray(int arr[][3], int rows) {
    for(int i = 0; i < rows; i++) {
        for(int j = 0; j < 3; j++) {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
}

int main() {
    int matrix[2][3] = {
        {1, 2, 3},
        {4, 5, 6}
    };
  
    // 第一维退化,第二维保持
    print2DArray(matrix, 2);
  
    // 查看大小差异
    printf("Sizeof matrix: %zu\n", sizeof(matrix));        // 输出:24
    printf("Sizeof matrix[0]: %zu\n", sizeof(matrix[0]));  // 输出:12
  
    // 指针类型示例
    int (*ptr_to_array)[3] = matrix; // 指向包含3个int的数组的指针
  
    return 0;
}

避免问题的实用技巧

  1. 使用结构体包装数组
typedef struct {
    int data[5];
    size_t size;
} ArrayWrapper;

void processArray(ArrayWrapper wrapper) {
    // 这里不会发生退化
    printf("Size in function: %zu\n", sizeof(wrapper.data));
}

int main() {
    ArrayWrapper wrapper = {{1, 2, 3, 4, 5}, 5};
    processArray(wrapper);
    return 0;
}
  1. 明确传递数组大小
void safeArrayProcessing(int *arr, size_t size) {
    for(size_t i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main() {
    int arr[] = {1, 2, 3, 4, 5};
    size_t size = sizeof(arr) / sizeof(arr[0]);
  
    safeArrayProcessing(arr, size);
    return 0;
}

总结

· 数组退化是C语言中将数组自动转换为指针的机制
· 主要发生在:函数参数传递、赋值给指针、表达式使用
· 不会发生在:sizeof、&运算符、字符串初始化字符数组
· 理解退化机制对于避免bug和编写正确的C代码至关重要
· 使用适当的技术(如结构体包装或显式传递大小)可以避免退化带来的问题

理解数组退化机制有助于编写更安全、更可预测的C代码,特别是在处理数组和指针时。


评论