使用malloc分配字符串精确大小的方法及常见疑问解答
C语言的字符串必须以不可见的空字符\0作为结束标记,所有内置的字符串操作函数(比如strcpy、strlen)都是靠检测到这个\0来判定字符串边界的。
而strlen(str)的返回值,是从字符串起始位置开始,数到第一个\0之前的所有可见字符总数,完全不包含这个结束符本身占用的空间。
你之前写的malloc((strlen(str) * sizeof(char))),申请的内存刚好只够放下字符串的可见字符,没有预留结束符的位置。当你用strcpy把字符串复制过去时,函数会在所有可见字符拷完后,额外向内存写入一个\0作为结束标记——这个写入位置已经超出了你申请的合法内存范围,属于堆缓冲区溢出,运行时崩溃是必然结果,没崩溃只是刚好碰到堆相邻的内存区域没有被占用而已,属于偶发的侥幸情况。
完全够。
要完整存储一个和源串完全一致的字符串,需要的精确内存大小就是:(strlen(源串) + 1) * sizeof(char)。因为C语言标准明确规定sizeof(char)的结果永远是1,所以就算不写这个乘法,计算结果也不会有区别。
你之前用strlen(str) + 10的写法能正常运行,本质是多预留了9个字节的冗余空间,刚好把strcpy要额外写入的\0兜住了,不是说必须预留10个字节才安全。
char str[100] = "string!"; malloc(sizeof(str))实际会分配多少内存? 这里的str是在栈上定义的、长度为100的固定大小字符数组,不是指向字符串的指针。对数组名使用sizeof操作符时,计算的是这个数组整体占用的总内存大小,和数组里实际存了多长的字符串没有任何关系。
也就是说这行代码会实打实申请100字节的堆内存,哪怕这个数组里实际存的"string!"只有7个可见字符、加上结束符也只占8个字节,数组剩下的92个字节是编译器自动初始化为0的,但不会改变数组本身的总大小。
容易混淆的场景:如果是
const char *str = "string!"这种指针定义,sizeof(str)计算的是指针本身的大小(64位系统下为8字节,32位系统下为4字节),和指向的字符串长度没有关系,这时候要获取字符串长度必须显式调用strlen。
- 只要涉及字符串存储,必须在strlen的结果基础上+1,预留空结束符的位置,这是C语言字符串开发中最容易踩的基础坑
- 不要靠堆内存的偶然冗余侥幸跑通逻辑,缓冲区溢出属于C语言标准定义的未定义行为,随时可能出现相邻内存被篡改、程序崩溃、甚至执行恶意代码的风险
- POSIX标准提供的
strdup函数已经帮你封装了「计算字符串长度、+1预留结束符位置、申请堆内存、拷贝字符串内容、补全结束符」的全流程,在兼容POSIX的环境下可以直接使用;如果需要跨平台兼容,手动实现这几行逻辑也非常简单。
内容的提问来源于stack exchange,提问作者Akari Oozora

