我之前问过这个问题,但没有提供一个可重复性最低的例子。感谢你的反馈。我试图向二进制文件中写入一个int,后面跟着一个布尔数组,其中int表示该数组的长度。
下面的代码编译后似乎可以正确生成二进制文件。当调用fread时,它设置了void* 参数,我将其传递给NULL,并触发了一个stack-smashing错误。
example.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后跟一个bools数组,其中int表示该数组的长度,然后加载回文件。
文件写得很正确,但是在阅读它的时候,我导致了堆栈崩溃。
3条答案
按热度按时间jchrr9hc1#
字符串
&ret.parts_cut
是ret.parts_cut
的地址,它本身是一个存在于 * 堆栈 * 上的指针,所以在询问这个问题时通常是四个或八个字节(当然可以更多)。如果你有更多的布尔值,将适合该指针的空间,那么你将损坏堆栈上的其他东西。
但是即使它 * 确实 * 适合,你几乎肯定仍然会得到不正确的行为,因为你的指针现在被设置为 * 不 * 指向你分配的内存,一组布尔值的一些奇怪的别名,如
0x0101000101000001
。如果您稍后尝试用类似
*(ret->parts_cut)
的东西去引用该指针,则不太可能有好的结果。你应该在
fread
调用中使用ret.parts_cut
,这是指针的 value,指向你刚刚分配了足够空间的东西:型
还有一点依赖
assert
宏来捕获可能导致问题的东西可能不是最好的主意。如果定义了NDEBUG
宏(它很可能在产品代码中),则assert
宏不做任何事情。您最好使用
if (! condition) handle_it()
而不是assert(condition)
来处理这些情况。gpnt7bae2#
除了@paxdiablo注意到
ret.part_cut
已经是一个指针并且您不能在fread()
中再次获取它的地址这一主要问题之外,还有其他一些地方可以改进:sizeof
,您可以在sizof(type)
中使用括号(例如,sizeof(bool)
),并且您可以省略sizeof object
的括号(例如sizeof p->len
)的数据。编译器通常允许这两种形式,但更准确地说,sizeof *p->parts_cut
。对于短代码,这通常不是问题,但当声明可能相隔数千行,或者指针指向具有多层间接寻址的对象时,这将变得更具挑战性,assert()
适合于短测试代码,但最好避免使用。为什么?它只是停止程序,阻止从错误中恢复。您通常需要一种方法来恢复您的程序,而不是简单地在每个错误时停止,fclose()
,以捕获通过检查写入的项数而未捕获的流错误(例如,将写入缓冲区内容刷新到磁盘时的文件流错误等),例如字符串
Cutlog ret = { .len = 0 };
(这会将len
设置为所示的值,并且所有其他成员未显式地初始化为零),read
是C语言中系统调用的名称,最好选择一个不会冲突的名称,例如size_t nparts = 0;
是相反的,written
的累积和来验证fwrite()
有点不太正统--但很有效。最好在每次写入后进行检查,以避免在第一次写入失败时写入到处于错误状态的流中(此更正由您来完成),fread()
不区分EOF
和stream-error,因此要找出发生了哪种错误,您需要在发生错误时调用feof()
和ferror()
,例如型
getc(fp)
来检查EOF
没有错,但也不是真的需要,"testbinary"
作为默认文件名,但实际上不需要为了写入不同的文件名而重新编译程序。你可以用一个简单的 * 三进制 * 来设置const char *path = argc > 1 ? argv[1] : "testbinary";
、save_cutlog()
会传回表示成功或失败的值,但您无法在main()
中使用该传回值,例如型
main()
返回之前,通过释放所有分配内存来整理。是的,它会在程序退出时被释放,但当使用内存检查工具(如valgrind
)时,在从main()
返回之前未释放的内存将在程序退出时显示为正在使用。总之,你知道你要去哪里,你只是在
fread()
中取ret.parts_cut
的地址时被绊倒了。如果您遵循上述所有建议,就可以调整程式码,如下所示:型
使用/输出示例
型
内存使用/错误检查
通过
valgrind
运行代码以捕获任何内存错误,并验证是否释放了所有内存(这也会捕获您最初的问题--以及在编译时启用 *full warnings *)。举例来说:型
请务必确认您已释放所有已分配的内存,并且没有内存错误。
如果你有任何问题,请告诉我。
ej83mcc03#
使用
clang -fsanitize=address
构建源代码会产生:字符串
解决方法是改变这一点:
型
对此:
型