C语言中字符数组为何能容纳超出初始化大小的字符?
我想在C语言中拼接两个字符数组,找到了如下代码。我知道数组初始化后无法调整大小,实现动态字符数组可使用动态内存分配(Dynamic Memory Allocation)。但代码中,char数组first初始化大小为8字节(含"\0"),将其传入concatenateStrings函数后,形参dest数组大小应为8字节,而大小为7字节的src数组被拼接到dest中,最终dest包含13个字符。执行程序时,sizeof(dest)为8,strlen(dest)为13,请问为何dest数组能容纳超出其初始化大小的字符?
附代码:
#include <stdio.h> #include <stdlib.h> void concatenateStrings(char dest[], const char src[]) { /* Find the end of the destination string (null terminator) */ int i = 0; while (dest[i] != '\0') { i++; } /* Copy characters from src to the end of dest */ int j = 0; while (src[j] != '\0') { dest[i++] = src[j++]; } /* Add null terminator to the concatenated string */ dest[i] = '\0'; printf("dest string: %d\n", sizeof(dest)); printf("dest len string: %d\n", strlen(dest)); printf("src string: %d\n", sizeof(src)); } int main() { char first[] = "Hello, "; char second[] = "World!"; printf("First string: %d\n", sizeof(first)); printf("Second string: %d\n", sizeof(second)); concatenateStrings(first, second); printf("Concatenated string: %s\n", first); return 0; }
解答
这个现象本质是数组越界访问的未定义行为,并非数组真的“容纳”了更多字符,以下是关键原因拆解:
函数参数中数组会退化为指针
当你把数组first传入concatenateStrings函数时,形参char dest[]本质上等价于char *dest——它只是一个指向数组首元素的指针,而非真正的数组。此时sizeof(dest)计算的是指针的大小(64位系统下为8字节,所以输出8),完全和原数组的实际大小无关。strlen只认
\0终止符strlen的工作逻辑是从起始地址开始计数,直到遇到第一个\0为止,它不会检查内存是否属于当前数组的合法空间。你的代码里把src的字符写到了first数组的边界之外,最后又添了一个\0,strlen就会从first的首地址一直数到这个新的\0,得到13的结果,但这部分超出的内存根本不是first数组的合法区域。越界访问只是“碰巧”没触发错误
C语言不会自动检查数组的边界,当你往first数组外面写数据时,实际上是在修改数组相邻的内存区域。如果这块内存刚好没有被其他变量或程序关键数据占用,程序可能暂时不会崩溃,还能正常输出结果,但这完全是运气——一旦这块内存被其他数据占用,就会导致程序崩溃、数据错乱等不可预测的问题,这就是C语言中的未定义行为。正确的做法是用动态内存分配
要安全拼接字符串,应该先计算两个字符串的总长度,然后用malloc或calloc申请足够的内存,拼接完成后记得用free释放内存,避免内存泄漏。
内容的提问来源于stack exchange,提问作者Jed

