为什么 fread 将输出指针设置为 NULL 并导致堆栈崩溃错误?

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

我之前问过这个问题,但未能提供最低限度可重复的示例。我很感激您的反馈。我正在尝试向二进制文件写入一个 int 后跟一个 bool 数组,其中 int 表示该数组的长度。

以下代码可以编译并且似乎可以正确生成二进制文件。当调用 fread 时,它会设置 void* 参数,我将其传递给 NULL 并触发堆栈崩溃错误。

示例.c

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

typedef struct cutlogTag{
    int len;
    bool *parts_cut;
} Cutlog;


size_t save_cutlog(const char *path, const Cutlog *p){
    
    FILE *fp;
    size_t written = 0;

    fp = fopen(path, "wb");
    if (fp == NULL){
        fprintf(stderr, "Failed to save cutlog file\n");
        return 0;
    }
    written = fwrite(&(p->len), sizeof(p->len), 1, fp);
    written += fwrite(p->parts_cut, sizeof(bool), p->len, fp);
    if(written != 1 + p->len)
        fprintf(stderr, "error writing file\n");
    else fprintf(stdout, "cutlog written to %s\n", path);
    fclose(fp);
    return written;
}

//returns cutlog with length of -1 on failure to load log
Cutlog load_cutlog(const char *path){
    
    Cutlog ret;
    FILE *fp;
    size_t read = 0;

    ret.len = -1;
    ret.parts_cut = NULL;
    
    fp = fopen(path, "rb");
    assert(fp != NULL);

    fseek(fp, 0, SEEK_SET);
    fread(&ret.len, sizeof(ret.len), 1, fp);
    ret.parts_cut = malloc(sizeof(bool) * ret.len);
    assert(ret.parts_cut);
    read = fread(&ret.parts_cut, sizeof(bool), ret.len, fp);
    if(read != ret.len){
        fprintf(stderr, "read unexpected size of data\n");
        ret.len = -1;
    }
    if (getc(fp) != EOF){
        fprintf(stderr, "expected file end. something went wrong. \n");
        ret.len = -1;
    }
    fclose(fp);
    return ret;
}

int main(int argc, char *argv[]){
    Cutlog clog;
    const char* path = "testbinary";
//initialize cutlog struct
    clog.len = 687;
    clog.parts_cut = malloc(sizeof(bool) * clog.len );
    assert(clog.parts_cut);
    for (int i = 0; i < clog.len; i++){
        clog.parts_cut[i] = false;
    }
//save to binary file and free from memory
    save_cutlog(path, &clog);
    free(clog.parts_cut);
//load from binary file
    clog = load_cutlog(path);
    fprintf(stdout, "len is %d\n", clog.len);
    return 0;
}

尝试编写一个二进制文件,一个 int 后跟一个 bool 数组,其中 int 表示该数组的长度,然后加载文件回来。

文件写入正确,但在读取时导致堆栈崩溃。

c fwrite fread binary-data
2个回答
0
投票
read = fread(&ret.parts_cut, sizeof(bool), ret.len, fp);

&ret.parts_cut
ret.parts_cut
堆栈上的地址。这是一个指针。因此,如果您有更多的布尔值适合指针的空间,则会损坏堆栈上的其他内容。

但即使它确实适合,你几乎肯定仍然会得到不正确的行为,因为你的指针现在被设置为not指向分配的内存,一组布尔值的一些奇怪的别名,如

0x0101000101000001

如果您稍后尝试使用类似

*(ret->parts_cut)
之类的内容取消引用它,这不太可能有好结果。

你应该使用

ret.parts_cut
,它是指针的,指向你刚刚分配的东西:

read = fread(ret.parts_cut, sizeof(bool), ret.len, fp);

简单说明一下:依靠

assert
宏来捕获可能导致问题的内容可能不是最好的主意。如果定义了
NDEBUG
(很可能在生产代码中),则
assert
宏完全不执行任何操作。

您最好使用

if (! condition) handle_it()
,而不是
assert(condition)
,来处理这些情况。


-1
投票

使用

clang -fsanitize=address
构建源代码会产生:

=================================================================
==485824==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fffd74354d0 at pc 0x558ecaa48b4e bp 0x7fffd7435490 sp 0x7fffd7434c60
WRITE of size 687 at 0x7fffd74354d0 thread T0
    #0 0x558ecaa48b4d in fread (/tmp/a.out+0x3cb4d) (BuildId: 532318f475d6a04e8d9ae1be96e2fd96a96490f1)
    #1 0x558ecaaeb41c in load_cutlog /tmp/t.c:48:12

Address 0x7fffd74354d0 is located in stack of thread T0 at offset 48 in frame
    #0 0x558ecaaeb11f in load_cutlog /tmp/t.c:32

  This frame has 1 object(s):
    [32, 48) 'retval' <== Memory access at offset 48 overflows this variable

解决办法是改变这个:

    read = fread(&ret.parts_cut, sizeof(bool), ret.len, fp);

对此:

    read = fread(ret.parts_cut, sizeof(bool), ret.len, fp);
© www.soinside.com 2019 - 2024. All rights reserved.