You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C语言存储系统命令输出到变长字符串数组时段错误排查

问题根因

不管是第一版还是修改后的代码,都存在几个致命的指针和内存逻辑错误,逐个说明:

第一版代码的核心bug

  • 指针步进逻辑完全错误:out是const char**类型,本质是存储char指针的数组,每个元素的大小是一个指针长度(64位系统下为8字节)。你写head += strlen(buffer),相当于每次步进几十上百个数组元素,直接越界访问非法内存。另外把指针强转成size_t做边界判断本身就是未定义行为,同类型指针直接比较就行,没必要转整数。
  • strcat用法完全错误:首先fgets读取内容时会自动在末尾补'\0'终止符,根本不需要手动追加。其次strcat要求第二个参数是合法的、以'\0'结尾的字符串,你传的&terminator是单个字符的地址,这个地址后续的内存值是随机的,strcat会一直向后遍历找0值,直接触发越界读。
  • 栈内存指针逃逸:你把*head指向了函数内的局部栈数组buffer,这个数组的生命周期只在exec函数内部,函数返回后对应的栈空间会被系统回收复用,外层访问这些指针全是野指针。

修改后第二版代码的遗留bug

你修正了索引步进的问题,但两个致命问题没解决:

  • 还是把所有out[i]都指向了同一个局部栈数组buffer:一方面函数返回后这些指针全是野指针,另一方面每次fgets读新内容都会覆盖buffer的内容,就算不考虑生命周期问题,最后所有指针指向的内容全是最后读取的那一行。
  • 打印循环硬遍历128次数组元素:你根本没记录实际读取到了多少行,那些没被赋值的buffer[i]都是未初始化的野指针,printf访问这些地址直接触发段错误。

修复方案

要独立存储每一行内容,必须给每一行单独分配堆内存(不能用函数内的局部栈数组),同时函数要返回实际读取到的行数,外层遍历的时候只访问有效元素,不要碰未初始化的指针。
可直接运行的修复后代码:

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

#define MAX_LINE_LENGTH 512

/*
 * 返回值说明:
 * 正整数:实际读取到的输出行数
 * -1:内存分配失败
 * 1:popen调用失败
 * 2:pclose调用失败
 */
int exec(const char* in, char** out, const size_t n)
{
    char buffer[MAX_LINE_LENGTH];
    FILE *file;
    size_t line_cnt = 0;

    if ((file = popen(in, "r")) == NULL) {
        return 1;
    }
    
    while (line_cnt < n && fgets(buffer, MAX_LINE_LENGTH, file) != NULL) {
        // 给当前行单独分配堆内存,长度是字符串长度+1(预留终止符位置)
        out[line_cnt] = malloc(strlen(buffer) + 1);
        if (out[line_cnt] == NULL) {
            pclose(file);
            // 出错时释放之前已分配的内存,避免泄漏
            for (size_t i = 0; i < line_cnt; i++) {
                free(out[i]);
            }
            return -1;
        }
        strcpy(out[line_cnt], buffer); // 将行内容拷贝到独立的堆内存
        line_cnt++;
    }

    if (pclose(file) == -1) {
        // 出错时统一清理内存
        for (size_t i = 0; i < line_cnt; i++) {
            free(out[i]);
        }
        return 2;
    }

    return (int)line_cnt;
}

#define N 128 
int main(void)
{
    char* buffer[N] = {0}; // 指针数组全初始化为空,避免野指针
    const char cmd[] = "ls -l"; // 替换成你需要执行的系统命令
    const int code = exec(cmd, buffer, N);
    
    // 错误分支处理
    if (code < 0) {
        perror("malloc failed");
        exit(EXIT_FAILURE);
    }
    if (code == 1) {
        perror("popen failed");
        exit(code);
    }
    if (code == 2) {
        perror("pclose failed");
        exit(code);
    }

    // 只遍历实际读取到的有效行,不访问未初始化的指针
    for (int i = 0; i < code; i++) {
        printf("%s", buffer[i]);
        free(buffer[i]); // 用完释放堆内存,避免泄漏
    }

    exit(EXIT_SUCCESS);
}

关键注意事项
  • 栈上局部变量的生命周期仅限所属函数,函数返回后对应内存会被回收,绝对不能把局部变量的地址传出函数给外部使用。
  • 指针步进的步长是由指针指向的类型决定的:char**类型指针+1,实际移动的字节数是sizeof(char*),不是1字节,更和读取到的字符串长度无关。
  • fgets会自动在读取内容末尾补'\0'终止符,不需要手动追加;如果读入的行包含换行符'\n',也会被一起存入缓冲区。
  • 遍历指针数组时必须明确有效元素的个数,禁止访问未初始化的野指针。
  • malloc分配的堆内存,用完必须对应调用free释放,避免内存泄漏。

内容的提问来源于stack exchange,提问作者Oh Fiveight

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 13:45:32