【问题标题】:Is this use of the Effective Type rule strictly conforming?这种使用有效类型规则是否严格符合?
【发布时间】:2017-10-05 18:04:53
【问题描述】:

C99 和 C11 中的有效类型规则规定,没有声明类型的存储可以用任何类型写入,存储非字符类型的值将相应地设置存储的有效类型。

抛开 INT_MAX 可能小于 123456789 的事实,以下代码对有效类型规则的使用是否严格符合?

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

/* Performs some calculations using using int, then float,
  then int.

    If both results are desired, do_test(intbuff, floatbuff, 1);
    For int only, do_test(intbuff, intbuff, 1);
    For float only, do_test(floatbuff, float_buff, 0);

  The latter two usages require storage with no declared type.    
*/

void do_test(void *p1, void *p2, int leave_as_int)
{
  *(int*)p1 = 1234000000;

  float f = *(int*)p1;
  *(float*)p2 = f*2-1234000000.0f;

  if (leave_as_int)
  {
    int i = *(float*)p2;
    *(int*)p1 = i+567890;
  }
}

void (*volatile test)(void *p1, void *p2, int leave_as_int) = do_test;

int main(void)
{
  int iresult;
  float fresult;
  void *p = malloc(sizeof(int) + sizeof(float));
  if (p)
  {
    test(p,p,1);
    iresult = *(int*)p;
    test(p,p,0);
    fresult = *(float*)p;
    free(p);
    printf("%10d %15.2f\n", iresult,fresult);
  }
  return 0;
}

根据我对标准的阅读,注释中描述的函数的所有三种用法都应该严格符合(整数范围问题除外)。因此代码应该输出1234567890 1234000000.00。然而,GCC 7.2 输出 1234056789 1157904.00。我认为当leave_as_int 为0 时,它将123400000 存储到*p1,然后将123400000.0f 存储到*p2,但我在标准中看不到任何可以授权这种行为的内容。是我遗漏了什么,还是 gcc 不合格?

【问题讨论】:

    标签: c gcc undefined-behavior c99 strict-aliasing


    【解决方案1】:

    是的,这是一个 gcc 错误。我已将它(带有简化的测试用例)归档为 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82697

    【讨论】:

    • 感谢您发布该错误。有趣的是,这个商店-商店订购错误似乎已经产生了如此快速的修复,但 82697 似乎需要更长的时间。我不知道该标准的作者是否打算要求进行必要的悲观化以认识到 *pi*pl 可能会别名即使没有证据表明它们可能,但肯定当存在混叠的证据时(如上例中的指针转换),认识到这种可能性并不是不合理的悲观,而是现实。
    • @supercat "悲观" "现实" ...为什么不是概率?您是在谈论与杀虫剂、药物一样的语言语义或风险评估吗?
    • 任何特定的程序要么是 100% 可靠的,即使编译器重新排序存储,或者它不会。没有什么是概率论的。与要求编译器在 6.5p7 未强制要求的某些其他情况下保留顺序的程序相比,在上述情况下存储顺序很重要的程序要少得多。在 C99 之前,标准的作者将识别一种类型的左值或指针从另一种类型的左值或指针派生的情况的能力视为纯粹的实现质量问题。 C99 添加的规则...
    • @curiousguy:......很难理解或处理并阻碍应该有用的优化,同时甚至无法处理简单直接的情况。更好的规则是,如果在函数或循环的任何特定执行期间更改了存储字节,则所有访问都必须使用左值完成,这些左值在此类执行期间从标识相同对象的指针或左值派生或同一个数组的成员。此外,所有使用派生左值访问存储字节的操作都必须先于使用与同一存储相关的任何非派生左值。
    • @supercat "函数的特定执行" 那么内联呢?
    【解决方案2】:

    生成的机器码无条件写入两个指针:

    do_test:
            cmpl    $1, %edx
            movl    $0x4e931ab1, (%rsi)
            sbbl    %eax, %eax
            andl    $-567890, %eax
            addl    $1234567890, %eax
            movl    %eax, (%rdi)
            ret
    

    这是一个 GCC 错误,因为所有商店都应为change the dynamic type of the memory accessed。我不认为这种行为是标准规定的;它是一个 GCC 扩展。

    您可以提交 GCC 错误吗?

    【讨论】:

    • 我对链接帖子的阅读表明,让存储更改对象的动态类型声明类型是一种扩展,但对象标准要求这种行为没有声明的类型[例如从malloc()] 接收到的指针。维护标准将要求编译器在通常会产生不必要的低效代码的情况下做出某些悲观假设,并且编译器提供不会考虑到的 non-conforming 模式是有意义的在这种情况下要更改对象的动态/有效类型。
    • 否则,我目前没有注册提交 gcc 错误,但也许我应该注册。但是,我不确定如果无法将行为描述为符合要求,应该提出什么作为补救措施。鉴于在某些情况下维护标准会不必要地阻碍优化,简单地说-fno-strict-aliasing 是完全符合性所必需的,并指定-fstrict-aliasing 仅可用于从不回收存储的代码。不幸的是,这意味着任何需要回收存储的代码都将被低效处理。
    猜你喜欢
    • 2018-02-22
    • 1970-01-01
    • 2020-08-01
    • 2020-04-11
    • 1970-01-01
    • 2017-12-10
    • 2017-05-08
    • 1970-01-01
    相关资源
    最近更新 更多