C语言直方图程序:最后一次循环字符串缓冲区被跳过问题
C直方图程序输出异常的原因分析
我编写了一个打印简单直方图的C程序,核心代码如下(完整代码超100行,需补充可说明):
#define LINE_CHAR '#' void printHistogram(FILE * const file, size_t const fields_size, char const * const * const names, unsigned const * const data) { // 复用缓冲区避免不必要的堆分配 size_t const name_buffer_size = max_string_length(fields_size, names) + 1; char name_buffer[name_buffer_size]; name_buffer[name_buffer_size - 1] = '\0'; // 数据条的缓冲区同理 size_t line_buffer_size = max_unsigned(fields_size, data) + 1; char line_buffer[line_buffer_size]; memset(line_buffer, LINE_CHAR, line_buffer_size); for (size_t i = 0; i < fields_size; i++) { size_t current_name_length = strlen(names[i]); // 填充剩余空间为空格 memset(name_buffer + current_name_length, ' ', name_buffer_size - 1 - current_name_length); // 复制名称到缓冲区(不带终止符) memcpy(name_buffer, names[i], current_name_length); // 仅打印所需长度的条形 line_buffer[data[i] + 1] = '\0'; fprintf(file, "%s|%s\n", name_buffer, line_buffer); // 恢复缓冲区内容 line_buffer[data[i] + 1] = LINE_CHAR; } } int main() { char const * const names[] = {"a", "b", "abc"}; unsigned const data[] = {5, 10, 15}; printHistogram(stdout, 3, names, data); }
预期输出:
a |##### b |########## abc|###############
实际输出:
a |###### b |########### |################
最后一次循环中名称缓冲区的内容被跳过,完全不显示,同时所有条形的#数量都比预期多1个,原因如下:
问题根源
数组越界写入破坏栈内存
处理最后一组数据(data[i]=15)时,line_buffer_size的值为max_unsigned(3, data)+1 = 15+1=16,因此line_buffer是长度为16的数组,有效索引范围是0~15。但代码中执行了line_buffer[data[i]+1] = '\0';,也就是line_buffer[16] = '\0';——这属于越界写入,会直接破坏栈上相邻内存区域的数据。栈变量的存储位置连续,越界写入的
'\0'恰好覆盖了name_buffer的起始字节,导致name_buffer变成空字符串,因此fprintf打印%s时完全没有输出名称。条形长度计算错误
代码中用line_buffer[data[i]+1] = '\0';截断条形,会导致打印出data[i]+1个#,而非预期的data[i]个。比如data[i]=5时,实际打印6个#,与预期不符。
修复方案
- 修正条形截断的索引:将
line_buffer[data[i]+1]改为line_buffer[data[i]],确保打印的#数量与data[i]一致。 - 避免数组越界:修改后,当
data[i]等于max_unsigned时,line_buffer[data[i]]是数组的最后一个有效索引(line_buffer_size-1),不会越界。
修复后的核心循环代码:
for (size_t i = 0; i < fields_size; i++) { size_t current_name_length = strlen(names[i]); memset(name_buffer + current_name_length, ' ', name_buffer_size - 1 - current_name_length); memcpy(name_buffer, names[i], current_name_length); // 修正截断索引,避免越界 line_buffer[data[i]] = '\0'; fprintf(file, "%s|%s\n", name_buffer, line_buffer); line_buffer[data[i]] = LINE_CHAR; }
内容的提问来源于stack exchange,提问作者yurich
相关产品推荐
相关产品推荐

