将 0 个字节读入 NULL 指针是否安全?

Is it safe to fread 0 bytes into a NULL-pointer?

在我的环境下,下面的代码运行的很好,但是能保证没问题吗?

FILE *file = fopen("in", "r");
int *read_to = NULL;
size_t count = 0;
size_t read = fread(read_to, sizeof(*read_to), count, file);
fclose(file);

分别

FILE *file = fopen("out", "w");
int *write = NULL;
size_t count = 0;
size_t written = fwrite(write, sizeof(*write), count, file);
fclose(file);

"N1570 Committee Draft — April 12, 2011 ISO/IEC 9899:201x" 7.21.8

中说

If size or nmemb is zero, fread returns zero and the contents of the array and the state of the stream remain unchanged.

If size or nmemb is zero, fwrite returns zero and the state of the stream remains unchanged.

根据以上文字,我认为它是有效的。

这不能保证有效,严格阅读标准会得出这样的结论:将空指针作为第一个参数传递给 fread()fwrite() 会导致未定义的行为。

根据§7.21.8.1 ¶2 of the C11 Draft Standard

size_t fread(void * restrict ptr, size_t size, size_t nmemb, FILE * restrict stream);

The fread function reads, into the array pointed to by ptr....

同样,在§7.21.8.2 ¶2中:

size_t fwrite(const void * restrict ptr, size_t size, size_t nmemb, FILE * restrict stream);

The fwrite function writes, from the array pointed to by ptr....

但是,在描述库函数使用的部分(§7.1.4)中指出:

If an argument to a function has an invalid value (such as a value outside the domain of the function, or a pointer outside the address space of the program, or a null pointer, or a pointer to non-modifiable storage when the corresponding parameter is not const-qualified) or a type (after promotion) not expected by a function with variable number of arguments, the behavior is undefined. If a function argument is described as being an array, the pointer actually passed to the function shall have a value such that all address computations and accesses to objects (that would be valid if the pointer did point to the first element of such an array) are in fact valid.

最后,在§4 ¶2

If a ''shall'' or ''shall not'' requirement that appears outside of a constraint or runtime- constraint is violated, the behavior is undefined.

由于空指针不指向有效对象,地址计算和访问对空指针无效,因此将空指针传递给 fread()fwrite() 违反了 "shall" 的 §7.1.4,因此行为未定义。

行为是否未定义取决于 null 参数是否会被视为 "invalid"。上面对行为的描述 完全描述了 当大小为零时 freadfwrite 的行为,以忽略数据指针的方式: fread(anything, 0,0, anything) 是 return 零,没有副作用——这个动作应该可以在不查看传入指针的情况下执行。

唯一的危险是一些编译器编写者可能无法识别 7.1.4 中的 "such as" 语言,因为它提供了错误程序可能以请求函数执行 "impossible" 操作(如从空地址读取或写入数据字节,或取消引用空指针或写入符合 const 限定的存储),而是将列表视为规范规范,指示此类参数无效 即使在他们自然会被忽略的情况下。虽然后一种处理是否会在人为的场景之外提供任何有意义的性能优势值得怀疑,但认为 "clever" 和 "stupid" 是反义词的编译器编写者可能会使用后一种处理来证明 "optimizations".

质量实现会将 fread(any,0,0,file) 视为空操作,而不考虑数据指针是否为空,因为这样做会以零成本提供一些好处。

关于使用 fwrite 写入各种数据的代码,包括由指针+size_t 组合标识的可选块,以及向其调用者证明 (null+0) 是一种有效的方式表示没有可选数据,应该对大小为零的情况使用特殊处理,我想这取决于一个人的代码是由高质量编译器还是 "clever" 编译器处理的。