C 使用有效类型规则是否严格符合要求?

C 使用有效类型规则是否严格符合要求?,c,gcc,undefined-behavior,c99,strict-aliasing,C,Gcc,Undefined Behavior,C99,Strict Aliasing,C99和C11中的有效类型规则规定,没有声明类型的存储器可以用任何类型写入,并且存储非字符类型的值将相应地设置存储器的有效类型 撇开INT_MAX可能小于123456789这一事实不谈,下面代码对有效类型规则的使用是否严格符合要求 #include <stdlib.h> #include <stdio.h> /* Performs some calculations using using int, then float, then int. If bot

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;
}
#包括
#包括
/*使用int和float执行一些计算,
然后是int。
如果两个结果都是理想的,则进行_测试(intbuff、floatbuff,1);
仅对于int,进行u测试(intbuff,intbuff,1);
仅对于浮动,进行_测试(floatbuff,float_buff,0);
后两种用法需要不声明类型的存储。
*/
无效do_测试(无效*p1,无效*p2,整数保留为整数)
{
*(int*)p1=1234000000;
浮点f=*(整数*)p1;
*(浮动*)p2=f*2-1234000000.0f;
if(保留为int)
{
int i=*(浮点*)p2;
*(int*)p1=i+567890;
}
}
void(*挥发性测试)(void*p1,void*p2,int leave_as_int)=do_测试;
内部主(空)
{
国际结果;
浮动法;
void*p=malloc(sizeof(int)+sizeof(float));
如果(p)
{
试验(p,p,1);
iresult=*(int*)p;
试验(p,p,0);
fresult=*(float*)p;
自由基(p);
printf(“%10d%15.2f\n”,iresult,fresult);
}
返回0;
}

从我对标准的理解来看,注释中描述的函数的所有三种用法都应该严格符合标准(整数范围问题除外)。因此,代码应输出
1234567890 1234000000.00
。然而,GCC 7.2输出的是
1234056789 1157904.00
。我认为当
leave_as_int
为0时,在将123400000.0f存储到
*p2
之后,它将123400000存储到
*p1
,但我在标准中没有看到授权此类行为的内容。我是否遗漏了任何内容,或者gcc不符合要求?

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

#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;
}
do_test:
        cmpl    $1, %edx
        movl    $0x4e931ab1, (%rsi)
        sbbl    %eax, %eax
        andl    $-567890, %eax
        addl    $1234567890, %eax
        movl    %eax, (%rdi)
        ret
这是一个GCC错误,因为所有的存储都应该是。我不认为这种行为是强制性的标准;它是一个GCC扩展


你能提交一个GCC错误吗?

是的,这是一个GCC错误。我把它(用一个简化的测试用例)归档为。

我对链接帖子的阅读表明,让存储更改具有声明类型的对象的动态类型是一种扩展,但是对于没有声明类型的对象(例如,从
malloc()
]接收的指针),标准要求这种行为。维护该标准要求编译器在通常会产生不必要的低效代码的情况下做出某些悲观的假设,并且编译器提供一种不一致的模式是有意义的,这种模式不会允许对象的动态/有效类型在这种情况下发生更改。否则,我目前还没有注册来归档gcc bug,不过也许我应该注册。不过,我不确定,如果无法将这种行为描述为合规行为,应该提出什么补救措施。考虑到在某些情况下维护标准会不必要地阻碍优化,简单地说,
-fn完全一致性不需要严格的别名,并指定
-fstrict别名
仅可用于从不回收存储的代码可能是可行的。不幸的是,这意味着任何需要回收存储的代码都会被低效地处理。谢谢你发布这个bug。有趣的是,这个商店订购错误似乎已经产生了这样一个快速修复,但82697似乎需要更长的时间。我不知道该标准的作者是否打算要求进行必要的悲观分析,以确认
*pi
*pl
可能存在别名,即使没有证据表明它们可能存在,但当存在别名的证据时(如上例中的指针强制转换)认识到这种可能性不是不合理的悲观,而是简单的现实。@supercat“悲观”“现实”。。。为什么不呢?你说的是语言语义还是风险评估,就像杀虫剂、药物一样?任何特定的程序要么100%可靠,即使编译器重新排序存储,要么就不会。这一点都不可能。在上述情况下,存储顺序很重要的程序远比要求编译器在6.5p7未强制要求的某些其他情况下保持顺序的程序少见。在C99之前,标准的作者将识别一种类型的左值或指针从另一种类型的左值或指针派生出来的情况的能力视为纯粹的实现质量问题。C99增加了一些规则,这些规则…@curiousguy:…很难理解,也很难处理和阻止应该是有用的优化,而即使是简单的情况也无法处理。更好的规则是,如果在函数或循环的任何特定执行期间更改了存储字节,则所有访问都必须使用左值来完成,这些左值是在执行期间从标识相同对象或相同数组成员的指针或左值派生的。此外,所有使用派生左值访问存储字节的操作都必须在使用与同一存储相关的非派生左值之前进行。@supercat“函数的特定执行”那么内联呢?