将结构体数组写入(fwrite)二进制文件时出现 Valgrind 错误

问题描述 投票:0回答:1

我的程序的目标是从标准输入中读取人员列表,并能够将它们写入和读取到二进制文件中。列表中的每个人都是一个包含三个字符串变量的 tPessoa 结构体。因为,我试图读取未指定数量的人,所以我使用“指针到指针”来创建一个结构数组。

当我使用 valgrind --leak-check=full --track-origins=yes` 执行编译的脚本时,出现以下错误:

==1211042== Syscall param write(buf) points to uninitialised byte(s)
==1211042==    at 0x4986A77: write (write.c:26)
==1211042==    by 0x48FCEEC: _IO_file_write@@GLIBC_2.2.5 (fileops.c:1180)
==1211042==    by 0x48FE9E0: new_do_write (fileops.c:448)
==1211042==    by 0x48FE9E0: _IO_new_do_write (fileops.c:425)
==1211042==    by 0x48FE9E0: _IO_do_write@@GLIBC_2.2.5 (fileops.c:422)
==1211042==    by 0x48FDFD7: _IO_file_close_it@@GLIBC_2.2.5 (fileops.c:135)
==1211042==    by 0x48F0D8E: fclose@@GLIBC_2.2.5 (iofclose.c:53)
==1211042==    by 0x1095A7: EscrevePessoasBinario (ex.c:68)
==1211042==    by 0x109704: main (ex.c:107)
==1211042==  Address 0x4a9e965 is 21 bytes inside a block of size 4,096 alloc'd
==1211042==    at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==1211042==    by 0x48F0BA3: _IO_file_doallocate (filedoalloc.c:101)
==1211042==    by 0x48FFCDF: _IO_doallocbuf (genops.c:347)
==1211042==    by 0x48FEF5F: _IO_file_overflow@@GLIBC_2.2.5 (fileops.c:744)
==1211042==    by 0x48FD6D4: _IO_new_file_xsputn (fileops.c:1243)
==1211042==    by 0x48FD6D4: _IO_file_xsputn@@GLIBC_2.2.5 (fileops.c:1196)
==1211042==    by 0x48F1FD6: fwrite (iofwrite.c:39)
==1211042==    by 0x109572: EscrevePessoasBinario (ex.c:60)
==1211042==    by 0x109704: main (ex.c:107)
==1211042==  Uninitialised value was created by a heap allocation
==1211042==    at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==1211042==    by 0x109384: LePessoasInput (ex.c:28)
==1211042==    by 0x1096EF: main (ex.c:106)
==1211042== 
==1211042== 
==1211042== HEAP SUMMARY:
==1211042==     in use at exit: 0 bytes in 0 blocks
==1211042==   total heap usage: 12 allocs, 12 frees, 14,666 bytes allocated
==1211042== 
==1211042== All heap blocks were freed -- no leaks are possible
==1211042== 
==1211042== For lists of detected and suppressed errors, rerun with: -s
==1211042== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

顺便说一句,我将完整的代码留在帖子的末尾,以便任何人都可以进行测试和调查。

这是我迄今为止发现的。

Valgrind 说我正在尝试对未初始化的值进行操作,我相信这意味着我可能会向二进制文件写入垃圾并触发错误。 Valgrind 说我在第 68 行这样做,但这似乎不正确。在第 68 行,调用

fclose(arquivo)
并且释放 FILE 指针(这里没什么不寻常的)。所以我认为错误是在该函数的其他地方,在注释掉某些部分后,我发现错误发生在
if ( fwrite(pessoas[pessoa], sizeof(tPessoa), 1, arquivo) != 1 )
上。这是有道理的,因为错误似乎与将未初始化的值写入 bin 文件有关。

Valgrind 还表示正在第 28 行创建统一值,

pessoas[(*nPessoas) - 1] = (tPessoa *) malloc(sizeof(tPessoa));
。在第 28 行,我为 pessoas 数组中的另一个 tPessoa 结构分配内存。但是,我看不出该结构如何未初始化,因为我在为其分配内存后立即将值传递给该结构的变量。我怀疑我为 nome (名称,英文)变量分配了 100 个字节,但我不一定传递 99 个字符加上 ' ',因为并非所有名称都那么长。因此,如果一个人的名字长度为 15 个字符,则意味着 16 个字节(算上“ ”),剩下 84 个字节未初始化。

问题可能是 nome 变量及其统一字符吗?如果是这样,我该如何解决这个问题。

如果没有,我将把整个代码留在下面,这样任何人都可以得出结论。欢迎任何建议。我还想更好地理解

Syscall param write(buf) points to uninitialised byte(s)
的真正含义。 Valgrind 错误对我来说有点神秘。

P.S.:我正在使用

gcc -g
编译代码。

代码

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

typedef struct ex
{
    char nome[100];
    char dataNascimento[11];
    char cpf[15];
} tPessoa;

void DesalocaPessoas(tPessoa** pessoas, int nPessoas) {
    for (int pessoa = 0; pessoa < nPessoas; pessoa++) {
        free(pessoas[pessoa]);
    }
    free(pessoas);
}

tPessoa** LePessoasInput(int* nPessoas) {
    tPessoa **pessoas = (tPessoa**) malloc(sizeof(tPessoa *));
    //pessoas[0] = (tPessoa*) malloc(sizeof(tPessoa));
    char controle = 0;

    while (1)
    {
        (*nPessoas)++;
        pessoas = (tPessoa **) realloc(pessoas, sizeof(tPessoa*) * (*nPessoas));
        pessoas[(*nPessoas) - 1] = (tPessoa *) malloc(sizeof(tPessoa));
        
        printf("----------- NOVA PESSOA -----------\n");
        printf("Nome: ");
        scanf("%[^\n]%*c", pessoas[(*nPessoas)-1]->nome);
        printf("Data de nascimento: ");
        scanf("%[^\n]%*c", pessoas[(*nPessoas)-1]->dataNascimento);
        printf("CPF: ");
        scanf("%[^\n]%*c", pessoas[(*nPessoas)-1]->cpf);
        while(1){
            printf("\nDeseja adicionar outra pessoa? [s/n] ");
            scanf("%c%*c", &controle);
            printf("\n");
            if ( controle == 'n' || controle == 'N' ) break;
            else if ( controle == 's' || controle == 'S' ) break;
        }
        if ( controle == 'n' || controle == 'N' ) break;   
    }

    return pessoas;
}

void EscrevePessoasBinario(tPessoa** pessoas, int nPessoas) {
    FILE *arquivo;
    int pessoasGravadas = 0;
    arquivo = fopen("pessoas.bin", "wb");

    if (arquivo == NULL) {
        return;
    }
    
    for ( int pessoa = 0; pessoa < nPessoas; pessoa++ ) {
        if ( fwrite(pessoas[pessoa], sizeof(tPessoa), 1, arquivo) != 1 ) {
            fclose(arquivo);
            return;
        }
        pessoasGravadas++;
    }
    
    printf("Pessoas gravadas: %d\n", pessoasGravadas);
    fclose(arquivo);
}

void ImprimePessoasBinario(int nPessoas) {
    FILE *arquivo;
    tPessoa* pessoaLida = (tPessoa*) malloc(sizeof(tPessoa));

    arquivo = fopen("pessoas.bin", "rb");

    if (arquivo == NULL) {
        free(pessoaLida);
        return;
    }

    int nPessoasLidas = 0;

    for (int pessoa = 0; pessoa < nPessoas; pessoa++) {
        if (fread(pessoaLida, sizeof(tPessoa), 1, arquivo) != 1) {
            break;
        }

        nPessoasLidas++;
        printf("-----------------------------------\n");
        printf("%s\n", pessoaLida->nome);
        printf("%s\n", pessoaLida->dataNascimento);
        printf("%s\n", pessoaLida->cpf);
        printf("-----------------------------------\n");
    }

    printf("Pessoas lidas: %d\n", nPessoasLidas);

    free(pessoaLida);
    fclose(arquivo);
}

int main() {
    int nPessoas = 0;

    tPessoa** pessoas = LePessoasInput(&nPessoas);
    EscrevePessoasBinario(pessoas, nPessoas);
    ImprimePessoasBinario(nPessoas);
    DesalocaPessoas(pessoas, nPessoas);

    return 0;
}
arrays c pointers struct malloc
1个回答
0
投票

在第 68 行,调用

fclose(arquivo)
并且释放
FILE
指针(这里没什么不寻常的)。

默认情况下,基于

FILE
的流会被缓冲,并且
fclose()
可以并且将会对打开的文件进行写入操作。根据 7.21.5.1 fclose 函数,C11 标准(草案)第 2 段(粗体矿井):

成功调用fclose函数会导致stream指向的流被刷新并关闭关联的文件。 流中任何未写入的缓冲数据都会被传送到主机环境以写入文件; ...

因此,对

fclose()
的调用可以将未初始化的内存写入您的文件,这意味着 Valgrind 识别它是正确的。

您确实应该检查从

fclose()
返回的值。

您还可以显式调用

fflush()
并检查其返回值以强制写入缓冲数据。

我怀疑我为 nome(名称,英文)变量分配了 100 个字节,但我不一定传递 99 个字符加上“”,因为并非所有名称都那么长。因此,如果一个人的名字长度为 15 个字符,则意味着 16 个字节(算上“ ”),剩下 84 个字节未初始化。

这实际上会导致读取然后写入未初始化的数据,因为从

malloc()
返回的字节明确未初始化

malloc函数为大小由size指定且值不确定的对象分配空间。

您可以调用

memset()
显式设置整个缓冲区的内容,也可以将
malloc()
替换为
calloc()
以确保在分配缓冲区时将整个缓冲区设置为零。假设您正在从用户处读取数据并执行文件 IO 操作,则性能差异根本无法衡量。

© www.soinside.com 2019 - 2024. All rights reserved.