编写struct + write(buf)指向未初始化的字节

Jua*_*uan 5 c

我在变量n0中为struct节点分配空间.我使用fwrite将此结构保存到文件,但是当我运行valgrind时,我收到此错误.我的代码如下,你能帮我吗?

==1412== Syscall param write(buf) points to uninitialised byte(s)
==1412==    at 0x4F22870: __write_nocancel (syscall-template.S:81)
==1412==    by 0x4EB0002: _IO_file_write@@GLIBC_2.2.5 (fileops.c:1261)
==1412==    by 0x4EB14DB: _IO_do_write@@GLIBC_2.2.5 (fileops.c:538)
==1412==    by 0x4EB0D5F: _IO_file_close_it@@GLIBC_2.2.5 (fileops.c:165)
==1412==    by 0x4EA4B0F: fclose@@GLIBC_2.2.5 (iofclose.c:59)
==1412==    by 0x400793: main (in /home/grados-sanchez/git/merkle-codigos-C/test_file)
==1412==  Address 0x402500c is not stack'd, malloc'd or (recently) free'd
==1412==  Uninitialised value was created by a stack allocation
==1412==    at 0x40073F: main (in /home/grados-sanchez/git/merkle-codigos-C/test_file)

typedef struct {
    unsigned char * ustr;
    int height;
}node;

void node_init(node * n, int r) {

    int i;
    n->ustr = malloc((r + 1) * sizeof(unsigned char));
    for (i = 0; i < r; i++) {
        (n->ustr)[i] = random() & 0xff;
    }
    (n->ustr)[r] = 0;
    n->height = -1;
}
void node_destroy(node * n) {
    free(n->ustr);
    n->height = -1;
}

int main() {
    FILE* file_ptr = fopen("file1", "w+");
    node n0;
    node_init(&n0,2);
    fwrite(&n0, sizeof(node), 1, file_ptr);
    fclose(file_ptr);
    node_destroy(&n0);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

Mar*_*oli 8

发生这种情况是因为编译器正在填充结构,并且您正在编写额外的填充字节但不会初始化它们.您可以通过首先运行程序,然后检查出来的内容来看到这一点:

$ od -x file1
0000000 92c0 04c2 0000 0000 ffff ffff 0000 0000
0000020
Run Code Online (Sandbox Code Playgroud)

前8个字节(92c0 04c2 0000 0000)是你的指针值,ustr(注意它正在写指针本身的值,而不是它指向的值,这可能不是你想要的,但这是一个单独的问题).

接下来的四个字节(ffff ffff)是您int height设置为-1的字节.

然后还有四个字节设置为0.这些是编译器插入的填充,您没有初始化.您可以通过稍微修改程序以使填充显式来向自己证明这种情况:

typedef struct {
    unsigned char * ustr;
    int height;
    int pad;
}node;

void node_init(node * n, int r) {

    int i;
    n->ustr = malloc((r + 1) * sizeof(unsigned char));
    for (i = 0; i < r; i++) {
        (n->ustr)[i] = random() & 0xff;
    }
    (n->ustr)[r] = 0;
    n->height = -1;
    n->pad = 0xdeadbeef;
}
Run Code Online (Sandbox Code Playgroud)

如果你现在运行程序,首先valgrind警告消失,第二个文件内容显示:

$ od -x file1
0000000 92c0 04c2 0000 0000 ffff ffff beef dead
0000020
Run Code Online (Sandbox Code Playgroud)

pad现在,变量的值显示为代替先前的零.

这一切都发生是因为编译器试图使结构的大小甚至是机器字大小的倍数,在您的情况下似乎是8字节(64位).

您可能不希望包含多余的pad变量,因此禁止警告的另一个选项是从一开始就清除整个结构:

typedef struct {
    unsigned char * ustr;
    int height;
}node;

void node_init(node * n, int r) {

    int i;

    /* Clear node struct to suppress valgrind warnings */
    memset(n, 0, sizeof(node));
    n->ustr = malloc((r + 1) * sizeof(unsigned char));
    for (i = 0; i < r; i++) {
        (n->ustr)[i] = random() & 0xff;
    }
    (n->ustr)[r] = 0;
    n->height = -1;
}
Run Code Online (Sandbox Code Playgroud)

这也会抑制valgrind警告,因为现在你正在初始化填充字节,并且文件中额外字节的内容会回到0,除非你现在明确地将它们设置为,而不是依赖于默认初始化:

$ od -x file1
0000000 92c0 04c2 0000 0000 ffff ffff 0000 0000
0000020
Run Code Online (Sandbox Code Playgroud)

编辑:

另一个实验要明确这一点.将以下内容添加到main()并再次运行:

printf("sizeof unsigned char *: %d\n", sizeof(unsigned char *));
printf("sizeof int: %d\n", sizeof(int));
printf("sizeof node: %d\n", sizeof(node));
Run Code Online (Sandbox Code Playgroud)

在64位Intel Linux上,我看到:

$ ./writer 
sizeof unsigned char *: 8
sizeof int: 4
sizeof node: 16
Run Code Online (Sandbox Code Playgroud)

结构大于其各部分的总和,你正在编写整个部分,但只是初始化部分.

编辑2:

在回答下面关于修复指针正在写入的问题而不是指向的问题时,您可以通过单独编写结构的字段而不是编写整个结构本身来解决这个问题.顺便说一句,这也将以不同的方式修复原始的valgrind问题,因为您将不再编写填充字节.所以你的主要看起来像这样:

int main() {
    FILE* file_ptr = fopen("file1", "w+");
    node n0;
    node_init(&n0,2);
    fwrite(n0.ustr, strlen(n0.ustr), 1, file_ptr);
    fwrite(&n0.height, sizeof(n0.height), 1, file_ptr);
    fclose(file_ptr);
    node_destroy(&n0);

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

如果你运行它并查看文件,它不再包含你的8字节指针,但只包含它指向的两个字节的数据:

$ od -x file1 
0000000 c667 ffff ffff
0000006
Run Code Online (Sandbox Code Playgroud)