当我想用它的地址初始化一个二维字符数组时出现段错误

Segfault when I want to initialize a 2d array of chars with its address

我正在处理一个项目,我需要在另一个函数中修改一个二维数组,而不是在我创建它的地方。但是,我在尝试这样做时遇到了段错误,我不明白这个问题。

这是我要执行的操作的代码:

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

void    test(char ***tab)
{
    *tab = malloc(sizeof(char *) * 2);
    *tab[0] = malloc(sizeof(char) * 5);
    *tab[0][0] = 't';
    printf("%c\n", *tab[0][0]);
    *tab[0][1] = 'e';
    printf("%c\n", *tab[0][1]);
    *tab[0][2] = 's';
    *tab[0][3] = 't';
    *tab[0][4] = '[=10=]';
    tab[1] = NULL;
}

int main()
{
    char **tab1;
    char **tab2;
    tab1 = malloc(sizeof(char *) * 2);
    tab1[0] = malloc(sizeof(char) * 5);
    tab1[0][0] = 't';
    tab1[0][1] = 'e';
    tab1[0][2] = 's';
    tab1[0][3] = 't';
    tab1[0][4] = '[=10=]';
    tab1[1] = NULL;
    printf("tab1 %s\n", tab1[0]);

    test(&tab2);
    printf("%s\n", tab2[0]);
}

我用以下代码编译我的代码:

gcc -fsanitize=address test.c

这是输出

tab1 test
t
AddressSanitizer:DEADLYSIGNAL
=================================================================
==12145==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x0000004c22c8 bp 0x7ffc58e533d0 sp 0x7ffc58e532c0 T0)
==12145==The signal is caused by a READ memory access.
==12145==Hint: address points to the zero page.
    #0 0x4c22c7 in test (/home/user42/Bureau/a.out+0x4c22c7)
    #1 0x4c2aaa in main (/home/user42/Bureau/a.out+0x4c2aaa)
    #2 0x7fc4aebc4b96 in __libc_start_main /build/glibc-2ORdQG/glibc-2.27/csu/../csu/libc-start.c:310
    #3 0x41aaa9 in _start (/home/user42/Bureau/a.out+0x41aaa9)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV (/home/user42/Bureau/a.out+0x4c22c7) in test
==12145==ABORTING

如您所见,tab1 在我初始化时不会导致任何段错误,但 tab2 在初始化第一个字符后会导致任何段错误。为什么会这样?

看起来像是运算符优先级问题:

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

void    test(char ***tab)
{
    *tab = malloc(sizeof(char *) * 2);
    (*tab)[0] = malloc(sizeof(char) * 5);
    (*tab)[0][0] = 't';
    printf("%c\n", (*tab)[0][0]);
    (*tab)[0][1] = 'e';
    printf("%c\n", (*tab)[0][1]);
    (*tab)[0][2] = 's';
    (*tab)[0][3] = 't';
    (*tab)[0][4] = '[=10=]';
    (*tab)[1] = NULL;
}

int main()
{
    char **tab1;
    char **tab2;
    tab1 = malloc(sizeof(char *) * 2);
    tab1[0] = malloc(sizeof(char) * 5);
    tab1[0][0] = 't';
    tab1[0][1] = 'e';
    tab1[0][2] = 's';
    tab1[0][3] = 't';
    tab1[0][4] = '[=10=]';
    tab1[1] = NULL;
    printf("tab1 %s\n", tab1[0]);

    test(&tab2);
    printf("%s\n", tab2[0]);
}

您可以使用 -ggdb3 进行编译以在消毒剂堆栈跟踪中获取更多信息,这将帮助您调试此类问题:

$ gcc foo.c -ggdb3
$ valgrind ./a.out
==121594== Memcheck, a memory error detector
==121594== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==121594== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==121594== Command: ./a.out
==121594== 
tab1 test
t
==121594== Use of uninitialised value of size 8
==121594==    at 0x109220: test (foo.c:10)
==121594==    by 0x109348: main (foo.c:32)
==121594== 
==121594== Invalid write of size 1
==121594==    at 0x109220: test (foo.c:10)
==121594==    by 0x109348: main (foo.c:32)
==121594==  Address 0x0 is not stack'd, malloc'd or (recently) free'd

Use of uninitialised value of size 8 at 0x109220: test (foo.c:10)准确告诉你问题出在哪里。

一样,tab[1]是导致未定义行为的越界访问。您可以在此处使用 (*tab)[1] = NULL;——首先取消对 *** 指针的引用以获得 **,然后将偏移量 1(概念上,第 1 行)设置为 NULL。

顺便说一句,不要忘记在使用后释放你的内存,sizeof(char)总是1所以你不需要它。

问题是一元运算符*[]运算符关联更松散,所以test中的大部分地方都需要加括号。以下修复它。

另请注意,tab[1] = NULL; 完全缺少 *。它需要是 (*tab)[1] = NULL;(见下文)。这是一个重要的修复,因为没有它,代码会出现未定义的行为:

void    test(char ***tab)
{
    *tab = malloc(sizeof(char *) * 2);
    (*tab)[0] = malloc(sizeof(char) * 5);
    (*tab)[0][0] = 't';
    printf("%c\n", (*tab)[0][0]);
    (*tab)[0][1] = 'e';
    printf("%c\n", (*tab)[0][1]);
    (*tab)[0][2] = 's';
    (*tab)[0][3] = 't';
    (*tab)[0][4] = '[=10=]';
    (*tab)[1] = NULL;
}