Malloc 没有分配足够的内存,尽管硬编码为只分配两个字符

Malloc is not allocating enough memory, despite being hardcoded to allocate exactly two characters

提问人:Joseph Latvala 提问时间:11/12/2023 更新时间:11/15/2023 访问量:119

问:

我有一个函数,给定一个指向 char 数组字符串(通过引用传递)、一个 char 字符(按值传递)和一个无符号短 int 长度(通过引用传递)的函数,如果字符串已经分配,则应该重新分配字符串一个大小更大的字符((length + 2) * sizeof(char)),然后将长度增加 1, 将最后两个字符分别设置为 character 和 null 终止符。如果尚未分配字符串,则在初始化字符之前,它会分配长度为两个字符 (2 * sizeof(char)) 的字符串。但是,当它设置最后一个字符(长度索引)时,它会出现段错误。

void appendCharacterToString(char** string, char character, unsigned short int * length) {

    if (*string != NULL) {
        *string = (char* ) realloc(*string, (*length + 2) * sizeof(char));
        ++*length;
    }
    else {
        *string = (char* ) malloc(2 * sizeof(char));
        *length = 1;
    }

    *string[*length - 1] = character;
    *string[*length] = 0;
}

在 gdb 中运行它时,我能够确定最初分配的数组,尽管被硬编码为分配两个字符的内存,但只分配一个字符,奇怪的是,它已经初始化为 null。

Program received signal SIGSEGV, Segmentation fault.
appendCharacterToString (string=0x7fffffffe0d8,
    character=110 'n', length=0x7fffffffe0d0)
    at hangmanstringutils.c:24
24          *string[*length] = 0;
(gdb)
(gdb) print string
$1 = (char **) 0x7fffffffe0d8
(gdb) print *string
$2 = 0x5555555592a0 "n"
(gdb) print *length
$3 = 1
(gdb) continue
Continuing.

Program terminated with signal SIGSEGV, Segmentation fault.
The program no longer exists.
(gdb) break hangmanstringutils.c:12
Breakpoint 1 at 0x555555555612: file hangmanstringutils.c, line 14.
(gdb) run
Starting program: /home/joseph/code/hangman/hangman
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".

Breakpoint 1, appendCharacterToString (
    string=0x7fffffffe0d8, character=110 'n',
    length=0x7fffffffe0d0) at hangmanstringutils.c:14
14          if (*string != NULL) {
(gdb) print *string
$4 = 0x0
(gdb) print *length
$5 = 0
(gdb) break 19
Breakpoint 2 at 0x55555555565a: file hangmanstringutils.c, line 19.
(gdb) continue
Continuing.

Breakpoint 2, appendCharacterToString (
    string=0x7fffffffe0d8, character=110 'n',
    length=0x7fffffffe0d0) at hangmanstringutils.c:19
19              *string = (char* ) malloc(2 * sizeof(char));
(gdb) print *string
$6 = 0x0
(gdb) print *length
$7 = 0
(gdb) break 21
Breakpoint 3 at 0x555555555677: file hangmanstringutils.c, line 23.
(gdb) continue
Continuing.

Breakpoint 3, appendCharacterToString (
    string=0x7fffffffe0d8, character=110 'n',
    length=0x7fffffffe0d0) at hangmanstringutils.c:23
23          *string[*length - 1] = character;
(gdb) print *string
$8 = 0x5555555592a0 ""
(gdb) print *length
$9 = 1
(gdb) print *string[0]
$10 = 0 '\000'
(gdb) print *string[1]
Cannot access memory at address 0x2c0
(gdb)
分段-故障 堆-内存 按引用传递 C 字符串

评论

3赞 Eric Postpischil 11/12/2023
print *string[1]不访问 索引 1 处的字符。它访问 指向的字符,该字符是 之后的内存中的字节。你想要。同样,在源代码中,您需要 .stringstring[1]*stringprint (*string)[1](*string)[length] = 0;
3赞 Eric Postpischil 11/12/2023
将来,在提出调试问题时,请提供最小的可重现示例
1赞 Toby Speight 11/12/2023
sizeof (char)根据定义,是 1,所以乘以它是无操作的。您可以放心地省略它。
0赞 Toby Speight 11/12/2023
与其使用 GDB,不如使用 Valgrind 来显示您首次访问越界内存的位置。

答:

2赞 Fe2O3 11/12/2023 #1

EDIT(在此答案之前)
OP 的问题源于无法识别运算符的优先级高于 。
[]*

因此,在取消引用之前进行处理。
该代码尝试取消引用垃圾地址并触发错误。
string[x]

简单的解决方法是放入其中一对: .例如:
(此函数中使用的间接性只会让读者感到困惑。
*string()(*string)[x]

正确的解决方法是让 OP 重写函数,从而消除不必要的间接复杂性......

继续我之前的回应......


代码太多了!我真的不想尝试了解该代码出了什么问题。

  1. void函数需要对两个参数使用间接。
  2. 避免复杂性是一个糟糕的选择。strlen()
  3. 从 & 中铸造回报是不好的做法void *malloc()realloc()
  4. 不阅读手册:其第一个参数为 。realloc()malloc()NULL
  5. sizeof(char)如果程序可以与较大的字符类型一起使用,则可能有意义。
  6. “相信”调用方没有不恰当地更改 的值。length
  7. 未验证堆分配是否成功。

然后,有这个:

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

char *appendCharacterToString( char *str, char ch ) {
    size_t len = str == NULL ? 0 : strlen( str );
    char *tmp = realloc( str, len + 2 );
    if( !tmp ) {
        perror( "allocation failed" );
        exit( EXIT_FAILURE );
    }
    str = tmp;

    str[ len++ ] = ch;
    str[ len   ] = 0;

    return str;
}

int main( void ) {
    char *str = NULL;

    str = appendCharacterToString( str, 'f' );
    str = appendCharacterToString( str, 'o' );
    str = appendCharacterToString( str, 'o' );
    puts( str );
    str = appendCharacterToString( str, 'b' );
    str = appendCharacterToString( str, 'a' );
    str = appendCharacterToString( str, 'r' );
    puts( str );

    free( str );

    return 0;
}

结果:

foo
foobar

请不要问为什么糟糕/过于复杂的代码不起作用。
培养对什么是正确、清晰、简洁的代码的感觉......什么不是......

评论

0赞 0___________ 11/12/2023
这里不需要 tmp,因为 str 是局部变量
1赞 Fe2O3 11/12/2023
@0_____ “函数不应该杀死程序” OP 可以决定在他们的情况下如何处理分配失败......由于该函数似乎支持游戏 Hangman,因此使用堆而不是堆栈缓冲区是另一个糟糕的选择......但是,谁能说呢?不是你,也不是我......
1赞 chux - Reinstate Monica 11/12/2023
@Fe2O3 “避免使用strlen()来支持复杂性是一个糟糕的选择>。”如果调用代码已经知道字符串长度(并且需要更新它),则传入长度的地址是合理的。使用类型很奇怪,但是在没有看到调用代码的情况下,最好的方法就太主观了。unsigned short
1赞 chux - Reinstate Monica 11/12/2023
@Fe2O3 很公平,我的朋友。关于“一次将一个字符附加到不断增长的堆缓冲区中的成本很高”——>我怀疑好的分配器处理这个问题的方式与用户代码一样好,因为它是一种常见的使用模式。随着岁月的流逝,我看到库代码变得越来越好,而我为超越它所做的努力在代码和维护方面更加昂贵。
1赞 Fe2O3 11/13/2023
@EricPostpischil en.wikipedia.org/wiki/Streetlight_effect PS:谢谢你提醒我向维基百科汇款 20 美元......一个非常有用的资源......:-)
-1赞 0___________ 11/12/2023 #2

此代码中存在许多问题。我强烈建议不要使用副作用。

char  *appendCharacterToString(char* string, char character, size_t *length) 
{
    size_t newLength = string ? *length + 2 : 2;
    string = realloc(string, newLength);

    if(string)
    {
        string[newLength - 2] = character;
        string[newLength - 1] = 0;
        *length = newLength - 1;
    }
    return string;
}

评论

1赞 Fe2O3 11/12/2023
当第 n 次分配失败时,如何处理内存泄漏?突然是 NULL 并且......你会信任哪个变量?string*length = ???
2赞 0___________ 11/12/2023
在这种情况下,@Fe2O3(你会相信哪个变量?绝对不是你对C的理解)不会改变。调用方的字符串引用之前分配的内存。函数字符串是本地的,它不会影响调用方的字符串。如果新分配失败,Realloc 不会销毁先前分配的块。:)length
0赞 Fe2O3 11/12/2023
啊哈!这是有道理的......有点......现在由调用者使用临时指针来捕获此函数的返回值(以防失败.........给猫剥皮的方法有很多......
0赞 0___________ 11/12/2023
@Fe2O3调用方必须知道分配是否失败并采取适当的操作。
1赞 chux - Reinstate Monica 11/12/2023
次要:更好,因为字符串的大小比它的长度大一,并且对象代表新的大小。newLengthnewSize
1赞 Eric Postpischil 11/12/2023 #3

print *string[1]不访问 索引 1 处的字符。它是 ,不是 .它访问 指向的字符,并表示 之后的内存字节。您需要在调试器命令中。同样,在源代码中,您需要:*string*(string[1])(*string)[1]string[1]string[1]*stringprint (*string)[1]

(*string)[*length - 1] = character;
(*string)[*length] = 0;