C语言如何创建数组?

来源:NoSQL教程作者:李修然头衔:网络博主
导读:本期聚焦于李修然创作的《C语言如何创建数组?》,敬请观看详情。数组是C语言里最基础也最容易出错的复合类型,它用一段连续内存保存同类型元素,下标从0开始。创建数组时不仅要确定元素类型和数量,还要明确数组所在的内存区域,这会直接影响生命周期和可修改长度。本文从栈上静态数组开始,说明声明语法、部分初始化和字符串数组的细节,再延伸到堆上的动态数组,对比 malloc、calloc、realloc 三种分配方式的适用场景与释放规则。随后分析数组作为函数参数时退化为指针的现象,以及变长数组的限制。读者可以据此避开数组越界、内存泄漏和长度丢失等常见问题,并在实际编码中选择合适的创建方案。

在C语言里,数组并不是一种自带长度属性的对象,而是同类型元素在内存中的连续排列。创建一个数组,本质上是告诉编译器或运行时环境:需要一段多大的连续空间,以及按什么类型去解读这些空间。由于C语言把内存管理权交给程序员,数组创建方式会直接影响它的作用域、生命周期以及能否在运行期改变大小。下面从栈上静态数组、堆上动态数组和函数传递三个角度展开。

C语言如何创建数组?

一、栈上静态数组:声明与初始化

静态数组通常指在函数内部或全局区通过常量表达式声明大小、由编译器自动分配空间的数组。它的基本语法是 元素类型 数组名[常量表达式]。方括号里的长度必须是编译期能确定的整型常量表达式,不能是运行期读取的变量。比如 int scores[5]; 会在栈上留出5个int大小的连续空间,而 double matrix[3][4]; 则创建一个3行4列的二维数组,实际内存同样是一段连续区域。

初始化数组有几个容易忽略的规则。如果初始化列表给出的元素少于数组长度,剩余元素会被自动置零,例如 int a[5] = {1, 2, 3}; 中后两个元素是0。如果不写长度而使用初始化列表,编译器会根据列表项数推导长度:int b[] = {3, 5, 7}; 的长度就是3。字符数组可以用字符串字面量初始化,但要注意 char str[] = "abc"; 实际占据4个字节,因为字符串末尾会自动追加一个 \0 结束符。若写成 char str[3] = "abc";,数组只能容纳 a、b、c 三个字符,缺少结束符,后续按字符串处理时很可能越界。

#include <stdio.h>

int main(void) {
    int scores[5] = {88, 92, 76};
    int b[] = {1, 2, 3};
    double matrix[3][4] = {0};

    for (int i = 0; i < 5; i++) {
        printf("%d ", scores[i]);
    }
    printf("\n");

    char str[] = "abc";
    printf("str bytes: %zu\n", sizeof(str));
    return 0;
}

这段代码中,scores只初始化了前三个元素,编译器会把后两个补0,因此循环输出为 88 92 76 0 0。二维数组使用 {0} 可以把所有元素统一置零。字符数组 str 使用自动推导长度,sizeof 输出4,印证了结束符的存在。理解栈上数组的自动存储期也很关键:函数返回后,数组所在栈内存会被回收,不能把指向局部数组的指针返回给调用者。

二、堆上动态数组:malloc、calloc、realloc

当数组长度需要由用户输入、文件内容或网络包大小决定时,静态数组就不够用了。C语言提供动态内存分配函数,在堆上按运行期长度创建数组。最常用的是 malloc,它只负责分配指定字节数的空间,不初始化内容。分配n个int元素的数组可以写成 int *arr = malloc(n * sizeof(int));。这里必须乘以 sizeof(int),因为malloc的参数是字节数而不是元素个数。如果分配失败,malloc会返回NULL,后续任何解引用都会导致未定义行为。

calloc 在分配的同时把每个字节置零,适合需要干净初始状态的缓冲区。它的参数是元素数量和单个元素大小:int *arr = calloc(n, sizeof(int));。相较之下,malloc分配后数据是未定义的,读这些位置可能得到随机值。另一个常用函数 realloc 用来调整已分配内存的大小,常常用于动态数组扩容。realloc的返回值必须重新接收,因为新的内存块地址可能与原来不同。安全的做法是先保存到临时指针,确认成功后再覆盖原指针,否则一旦失败原指针也会丢失,造成内存泄漏。

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int n;
    printf("请输入数组长度:");
    scanf("%d", &n);

    int *arr = malloc(n * sizeof(int));
    if (arr == NULL) {
        fprintf(stderr, "内存分配失败\n");
        return 1;
    }

    for (int i = 0; i < n; i++) {
        arr[i] = i * i;
    }

    int new_n = n + 5;
    int *tmp = realloc(arr, new_n * sizeof(int));
    if (tmp == NULL) {
        free(arr);
        fprintf(stderr, "扩容失败\n");
        return 1;
    }
    arr = tmp;

    for (int i = n; i < new_n; i++) {
        arr[i] = 0;
    }

    free(arr);
    arr = NULL;
    return 0;
}

示例里先根据输入n分配数组,再通过realloc扩容5个元素。注意扩容后新增部分不会被calloc那样自动清零,所以循环把新元素手动置0。释放内存时使用 free(arr);,之后设置 arr = NULL; 可以降低重复释放或使用悬空指针的风险。动态数组的优势是大小灵活,缺点是必须手动管理释放,否则容易造成内存泄漏。

三、数组退化为指针:函数参数中的关键细节

数组创建之后,经常要传给函数处理。但C语言里一个关键规则是:当数组名作为函数参数传递时,它会退化为指向首元素的指针,原本的长度信息在函数内部完全丢失。也就是说,即使形参写成 int arr[10],编译器也按 int *arr 处理,10这个数字不会参与类型检查。此时在函数内部用 sizeof(arr) 得到的是指针大小,通常是4或8字节,而不是整个数组的字节数。

因此,传递数组时通常需要额外传递长度参数。比如打印函数可以设计为 void print_array(int *arr, size_t len),调用前用 sizeof(nums) / sizeof(nums[0]) 计算元素数量。这种方法只对当前作用域内可见的数组有效,对已经退化为指针的数组不能再度使用。若数组是多维的,例如 int matrix[][4],除第一维外的维度必须写清楚,因为编译器需要根据列数计算每行步长,否则无法正确索引。

#include <stdio.h>

void print_array(int *arr, size_t len) {
    for (size_t i = 0; i < len; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main(void) {
    int nums[] = {10, 20, 30, 40};
    size_t len = sizeof(nums) / sizeof(nums[0]);
    print_array(nums, len);
    return 0;
}

这段程序说明了长度传递的标准姿势。main函数中nums还没有退化,所以能通过sizeof求出元素个数;进入print_array后,arr已经是指针,必须依靠len来控制循环边界。若忘记传递长度,函数要么写死长度,要么越界读取,这正是很多缓冲区漏洞的起点。理解数组退化为指针,也有助于理解为什么不能从函数返回局部数组,但可以返回malloc分配到堆上的数组。

四、常见创建错误与规避方法

创建数组时最常见的错误是越界访问。C语言不做边界检查,写入 arr[n] 时如果下标超出数组范围,可能覆盖相邻变量、破坏栈结构甚至被安全攻击利用。避免方式包括:循环条件使用常量长度或显式传递的len;对用户输入的上限做检查;使用静态分析工具和编译器的地址消毒器辅助定位。

另一个问题是混淆字符串数组与字符指针。比如 char *p = "hello"; 创建的是指向字符串字面量的指针,内容通常不可修改;而 char arr[] = "hello"; 创建的是可修改的字符数组。两者的sizeof、赋值能力和修改行为都不同。需要变长字符串时,优先考虑堆上分配 malloc(strlen(src) + 1),注意为 \0 留出空间。

还有一类隐蔽问题是使用变长数组,即C99允许的 int n = 10; int arr[n];。这种方式虽然创建简单,但数组存储在栈上,长度过大容易造成栈溢出,而且不是所有编译器或标准都支持得很好。在需要运行期长度时,动态内存分配通常是更通用、更可控的选择。规范地创建、传递和释放数组,是C语言程序员处理连续数据的基本功。

C语言数组数组初始化动态内存分配修改时间:2026-09-22 05:18:06

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/0922/60352.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。