我之前问过这个问题,但未能提供最低限度可重复的示例。我很感激您的反馈。我正在尝试向二进制文件写入一个 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 表示该数组的长度,然后加载文件回来。
文件写入正确,但在读取时导致堆栈崩溃。
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)
,来处理这些情况。
使用
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);