C语言固定大小字符数组拼接字符串输出带多余空格的原因及优化方案
异常输出根因说明
很多人遇到这个问题时会纠结异常到底是full_name未初始化导致的,还是strcat的工作机制导致的,实际上这两个因素是联动产生了最终的异常效果,不存在非此即彼的关系:
full_name未初始化是问题的根源诱因:full_name是main函数内的局部栈数组,没有任何显式初始化,数组内的内存字节是之前栈帧运行残留的随机值,既不保证起始位置就是合法的字符串终止符'\0',也不保证内存里的内容是可预期的。如果直接从数组开头按位置写入内容(不做扫描),哪怕数组没初始化,也不会出现残留空格的问题;但因为数组里本身没有合法的终止符,才给后续的字符串操作留下了读到垃圾值的可能。strcat的工作机制是异常被放大的直接原因:strcat完全不感知目标数组的固定大小,它的执行逻辑是从目标内存的起始地址逐字节扫描,直到碰到第一个'\0',才会把源字符串的内容拷贝到这个位置。如果目标内存是合法初始化的空字符串,这个扫描过程会直接在起始位置碰到'\0',不会产生任何问题;但如果目标内存未初始化,这个扫描过程没有任何边界,会一路读到残留的可打印字符,直到碰巧扫到一个'\0'才停下来,属于C标准定义的未定义行为。
放到原代码的场景里:第一次调用strcat(full_name, first_name)时,strcat从full_name的开头漫无目的地找'\0',中途碰到大量残留的空格字节(ASCII 0x20)时会一直保留这些值,直到碰巧扫到一个是'\0'的字节位置才开始拷贝内容。后续puts(full_name)输出时,会从内存起始位置一直输出到最终的'\0'位置,自然就把中间一大段残留的空格全部带出来,形成了“名和姓之间隔了大段空格”的异常输出。
更合理的固定长度字符数组拼接实现
要彻底解决这个问题,只需要做几个关键修正,就能在固定数组大小的前提下安全完成拼接:
核心修正原则
- 拼接前必须保证目标字符串是合法的空串:也就是从数组起始位置就写入'\0',给所有字符串操作提供明确的起始终止位置。最简单的初始化方式是定义数组时直接赋值空串:
char full_name[40] = "";这种写法会自动把数组所有剩余字节置0,不会留下残留值。 - 尽量不用多次无边界的
strcat调用:strcat不会检查目标数组的总长度,哪怕目标数组已经被初始化,只要待拼接的两个字符串总长度超过数组大小,就会发生越界写入。优先使用带长度限制的安全拼接接口。 - 处理
fgets的固有行为:fgets会把用户输入时按的回车对应的'\n'一起读到字符串里,不处理的话会导致输出出现多余换行。
参考实现代码
#include <stdio.h> #include <string.h> int main(void) { char first_name[20], last_name[20], full_name[40]; // 显式初始化full_name为合法空字符串 *full_name = '\0'; puts("First Name?"); fgets(first_name, sizeof(first_name), stdin); // 去掉fgets读入的换行符 first_name[strcspn(first_name, "\n")] = '\0'; puts("Last Name?"); fgets(last_name, sizeof(last_name), stdin); last_name[strcspn(last_name, "\n")] = '\0'; // 带长度限制的安全拼接,自动添加字符串终止符 snprintf(full_name, sizeof(full_name), "%s %s", first_name, last_name); puts(full_name); return 0; }
实现的优势
- 从根源上消除了未初始化内存的影响,不管栈上残留什么值,都不会被字符串操作读到。
snprintf会严格按照传入的数组最大长度写入内容,就算输入的名、姓长度加起来超过40字节,也不会发生越界写入,同时会自动补全字符串末尾的'\0'。- 兼容了
fgets的读入特性,输出的字符串不会带多余的换行符。
内容的提问来源于stack exchange,提问作者David542
相关产品推荐
相关产品推荐

