【问题标题】:Is it safe to fread 0 bytes into a NULL-pointer?将 0 字节读入 NULL 指针是否安全?
【发布时间】:2019-01-17 16:23:24
【问题描述】:

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

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);

【问题讨论】:

    标签: c null language-lawyer fread


    【解决方案1】:

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

    根据§7.21.8.1 ¶2 of the C11 Draft Standard

    size_t fread(void * 限制 ptr, size_t 大小,size_t nmemb, FILE * 限制流);

    fread 函数读取 ptr....

    指向的数组

    同样,§7.21.8.2 ¶2:

    size_t fwrite(const void * 限制 ptr, size_t 大小,size_t nmemb, FILE * 限制流);

    fwrite 函数从 ptr....

    指向的数组写入

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

    如果函数的参数具有无效值(例如函数域外的值,或程序地址空间外的指针,或空指针,或指向不可修改存储的指针)相应的参数不是 const 限定的)或具有可变数量参数的函数不期望的类型(提升后),则行为未定义。 如果函数参数被描述为数组,则实际传递给函数的指针应具有一个值,使得所有地址计算和对对象的访问(如果指针确实指向此类对象的第一个元素,则该值将是有效的)一个数组)实际上是有效的。

    最后,在§4 ¶2

    如果违反了出现在约束或运行时约束之外的“应”或“不应”要求,则行为未定义。

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

    【讨论】:

      【解决方案2】:

      “N1570 委员会草案 — 2011 年 4 月 12 日 ISO/IEC 9899:201x”在 7.21.8 中说

      如果 size 或 nmemb 为零, fread 返回零并且数组的内容和流的状态保持不变 不变。

      如果 size 或 nmemb 为零, fwrite 返回零,流的状态保持不变。

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

      【讨论】:

      • 这些语句只说函数在长度为零时起作用,而不是在指针为空时起作用。根据前面段落中指定约束的语句,ptr 参数指向一个数组。如果使用空指针调用,则ptr 不指向数组,因此从技术上讲这违反了约束。当然,freadfwrite 的直接实现可能不会关心当长度为零时ptr 是否为空,但问题是它是否保证
      【解决方案3】:

      行为是否未定义取决于空参数是否被视为“无效”。上面对行为的描述完全描述freadfwrite在大小为零时的行为,忽略了数据指针:fread(anything, 0,0, anything)的作用是返回零没有副作用——一个动作应该可以在不查看传入的指针的情况下执行。

      唯一的危险是一些编译器编写者可能无法将 7.1.4 中的“例如”语言识别为提供错误程序可能以要求函数执行“不可能”操作的方式传递的事物的示例(例如从空地址读取或写入数据字节,或取消引用空指针或写入 const 限定的存储),而是将列表视为规范规范,表明此类参数是无效的 ,即使在它们会自然会被忽略。尽管后一种处理方式能否在人为的场景之外提供任何有意义的性能优势值得怀疑,但认为“聪明”和“愚蠢”是反义词的编译器编写者可能会使用后一种处理方式来证明“优化”的合理性。

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

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

      【讨论】:

      • 说到 null+0,我认为空指针上的指针算术是 UB,即使它只是加 0,我也不会惊讶地发现即使是“质量”fread 实现计算 @987654325 @ 当count 为 0 时。
      • @user2357112:在 C++ 中定义了向空指针添加 0 的行为(它产生一个空指针)。 C 标准将 允许 实现在将 0 添加到 null 时以任意方式表现,并且在某些平台上这可能是一种自然行为,但这并不意味着质量实现不应该对待ptr+0 为所有指针值产生 ptr,这将是自然平台行为。
      • @user2357112:人们没有意识到的是,从 C89 的基本原理来看,C89 的作者并不认为标准需要强制所有编译器已经在做的事情 。他们希望编译器编写者能够认识到,如果某个特定的惯用语在特定平台和领域的程序中流行,那么针对该平台和领域的高质量实现将支持它,无论标准是否强制要求。如果标准做出任何努力,允许程序员通过“流行的扩展”做所有可能的事情,但没有......
      • ...使用 UB(如果没有别的,#ifndef ZERO_SIZE_FREAD_ALWAYS_NOP/#error SORRY/#endif),那么狭义地解释标准可能是合理的。否则,我会质疑为什么有人应该对在某些情况下跳轨是否“符合”更感兴趣,而不是它是否会使编译器变得不那么有用
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-09
      • 2010-10-30
      • 2015-12-21
      • 2015-10-14
      相关资源
      最近更新 更多