【问题标题】:Out of bounds memory address in the middle of 2D array?二维数组中间的内存地址越界?
【发布时间】:2012-06-07 12:41:02
【问题描述】:

首先,对不起标题。我真的不太确定如何表达它。

在 C 中,我有一个 2D 字符串数组,声明和分配如下:

char ** args = malloc(50*(num_args+1));
for (int i = 0; i < num_args+1; i++){
    args[i] = malloc(50);

我在一种“基本的 shell”类型程序中使用它,模仿 bash 的一些特性,因此是 num_args 变量。

在多台机器上编译运行,args[4]处的地址总是越界。这是相关的 gdb 输出:

(gdb) print args[0]
$2 = 0x609140 "gcc"
(gdb) print args[1]
$3 = 0x609180 ""
(gdb) print args[2]
$4 = 0x6091c0 ""
(gdb) print args[3]
$5 = 0x609200 ""
(gdb) print args[4]
$6 = 0x636367 <Address 0x636367 out of bounds>
(gdb) print args[5]
$7 = 0x609280 ""

如您所见,args[4] 之前和之后的地址都是有效的。这个地址怎么会越界?

使用此代码的整个函数是here及以下:

void parse(const char * command){
    // first parse built-ins (ie, not a call to the OS)
    if (strcmp(command, "history") == 0){
        show_history();
        return;
    }
    if (strcmp(command, "exit") == 0){
        exit(0);
    }

    hist_add(command);

    // copy 'command' into arg_string, while ignoring any possible comments
    char * arg_str;
    int num_args = 1;
    arg_str = malloc(strlen(command));
    for (int i = 0; i < strlen(command); i++){
        if (command[i] == '#' || command[i] == '\n') break;
        if (command[i] == ' ') num_args++;
        arg_str[i] = command[i];
    }

    // split arg_str into a string array where each string is an argument
    // to the command
    char ** args = malloc(num_args+1);
    for (int i = 0; i < num_args+1; i++){
        args[i] = malloc(50);
    }
    int tokens = 0;
    const char token = ' ';
    char * next = strtok(arg_str, &token);
    while (next != NULL){
        strcpy(args[tokens++], next);
        next = strtok(NULL, &token);
        if (next == NULL)
            args[tokens] = (char *)NULL;
    }

    exec_command(args);
}

【问题讨论】:

  • 您显示的调试语句的num_args 的值是多少?
  • 某些东西在你的指针上复制了字母“ccg”(或者可能是“gcc”)。您显示的代码不是问题的根源。
  • 对不起,我应该补充一点,在调试程序时正在解析一串文本。 “gcc”是故意存在的。完整函数的代码显示了 num_args 的来源(pastebin 链接)。

标签: c memory malloc


【解决方案1】:

您的问题的答案在于它不是二维数组。相反,args 包含一个指向一维指针数组的第一个元素的指针,并且每个元素本身都可以指向一维数组 char 的元素(这通常称为“不规则数组”,因为这些一维数组可以有不同的长度)。

所以一个地址args[4] 可以越界,即使args[3]args[5] 不是,是因为三个指针args[3]args[4]args[5] 是完全独立的值。

args[4] 很可能被不正确的值覆盖,因为它实际上位于您分配的区域之外 - 您没有为args 指向的数组分配足够的空间。您的malloc() 调用请求num_args + 1 字节,但您希望有足够的空间用于num_args + 1 指针,每个指针占用一个以上字节。我建议将您的 malloc() 呼叫更改为:

char ** args = calloc(num_args + 1, sizeof args[0]);

(您当然可以自己将num_args + 1 乘以sizeof args[0] 并调用malloc(),而不是使用calloc(),但是如果您这样做,则需要检查以确保乘法不会溢出@ 987654340@.calloc() 应该为您处理)。

【讨论】:

  • 感谢您详细解释的答案。我不知道 calloc,实际上我对手动内存管理(以及 C)还很陌生。这非常有效。
【解决方案2】:

malloc() 的参数是要分配的字节数。我猜num_args 不足以容纳char * 类型的所有指针,而50 也不足以考虑您的字符串长度。我没有详细查看您的完整代码,但您可能需要执行malloc(sizeof(char *) * num_args) 来分配所有指向参数字符串的指针。然后循环并使用malloc(sizeof(char) * len) 为每个字符串(如果它们被复制)分配足够的空间,其中len 是您需要存储的字符串的最大长度。

【讨论】:

  • 添加sizeof(char **) * num_args(或简称(char*))是对的,因为指向指针的指针与指针大小相同)。但是,char 的大小不总是 1 字节吗?我应该补充一点,神秘的 args[4] 内存地址仍然不可寻址。
  • num_args 的值是多少?因为如果小于 5,您将没有足够的空间分配给args[4]
  • 忽略我上一条消息的最后一部分。我在一台机器上编辑了代码,并在我要 ssh 的机器上运行了旧的二进制文件。此外,我在想对 malloc 的初始调用是“我应该保留多少指针?”而不是多少字节。感谢您清理它。
  • @callinyouin,没问题。另外,回答您的另一个问题:是的,我相信 C 标准保证 sizeof(char) 返回 1,但指向 char 的指针可能比这更大。尽管如此,我认为将sizeof 与适当的类型参数一起使用是一种很好的做法,即使只是为了代码清晰。当然,malloc(sizeof(char))malloc(1) 按照标准应该总是相同的,但前者看起来更清晰。
【解决方案3】:

以下行中可能存在内存分配错误和性能错误:

arg_str = malloc(strlen(command));
for (int i = 0; i < strlen(command); i++){

通常有一个函数strdup() 可用于复制字符串。当它不可用时,您使用:

char *arg_str = malloc(strlen(command) + 1);

也为终端 null '\0' 留出足够的空间。

性能错误是,如果字符串很长,则在循环的每次迭代中评估 strlen() 的成本会很高。计算一次长度并重复使用它——除非字符串的长度在每次迭代中都不同。

你不是 null 终止你的字符串;这样做很重要。

int len = strlen(command);
int i;  // Must be in scope after the loop ends
for (i = 0; i < len; i++){
    if (command[i] == '#' || command[i] == '\n') break;
    if (command[i] == ' ') num_args++;
    arg_str[i] = command[i];
}

// i is out of scope if you use for (int i = 0;...
arg_str[i] = '\0';

没有空终止可能是您其他问题的原因。如有疑问,请随时打印内容,但请小心不要让字符串为 null 终止。

【讨论】:

    猜你喜欢
    • 2014-03-04
    • 2019-11-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-19
    相关资源
    最近更新 更多