【问题标题】:Is this behavior of clang standard compliant?这种clang标准的行为是否符合?
【发布时间】:2016-12-03 10:11:05
【问题描述】:

这将是一个冗长的语言律师问题,所以我想快速说明为什么我认为它相关。我正在从事一个项目,其中严格遵守标准至关重要(编写一种编译为 C 的语言)。我要给出的示例似乎是 clang 的标准违规,因此,如果是这种情况,我想确认一下。

gcc 表示带有指向限制限定指针的条件的条件不能与带有 void 指针的条件语句共同存在。另一方面,clang 可以很好地编译这些东西。这是一个示例程序:

#include <stdlib.h>

int main(void){
   int* restrict* A = malloc(8);
   A ? A : malloc(8);
   return 0;
   }

对于 gcc,选项 -std=c11-pedantic 可以包含或不包含在任何组合中,对于 clang 以及选项 -std=c11-Weverything 也是如此。无论如何,clang 编译没有错误,gcc 给出以下内容:

tem-2.c: In function ‘main’:
tem-2.c:7:2: error: invalid use of ‘restrict’
  A ? A : malloc(8);
  ^

c11 标准对条件语句做了以下说明,强调:

6.5.15 条件运算符

...

  1. 第二个和第三个操作数应满足以下条件之一:

——两个操作数都有算术类型;

——两个操作数具有相同的结构或联合类型;

——两个操作数都是void类型;

——两个操作数都是指向兼容类型的合格或不合格版本的指针;

——一个操作数是一个指针,另一个是一个空指针常量;或

一个操作数是指向对象类型的指针,另一个是指向限定或非限定版本的 void 的指针。

...

  1. 如果第二个和第三个操作数都是指针,或者一个是空指针常量,并且 other 是指针,结果类型是指向具有所有类型限定符的类型的指针 两个操作数引用的类型。此外,如果两个操作数都是指向 兼容类型或兼容类型的不同限定版本,结果类型为 指向复合类型的适当限定版本的指针;如果一个操作数是 空指针常量,结果具有另一个操作数的类型;否则,一个操作数 是指向 void 或 void 的限定版本的指针,在这种情况下,结果类型是 指向适当限定版本的 void 的指针。

...

在我看来,上面的第一个粗体部分表示这两种类型可以一起使用,第二个粗体部分将结果定义为指向限制限定版本 void 的指针。但是,如下所示,该类型不存在,因此表达式被 gcc 正确识别为错误:

6.7.3 类型限定符,第 2 段

指针类型以外的类型,其引用类型是对象类型,不应限制限定。

现在,问题是这个示例程序违反了“不得”条件,因此需要通过以下方式产生错误:

5.1.1.3 诊断,第 1 段

一个符合要求的实现应产生至少一个诊断消息(在 实现定义的方式)如果是预处理翻译单元或翻译单元 包含违反任何语法规则或约束的行为,即使该行为也是显式的 指定为未定义或实现定义。诊断消息不需要 在其他情况下产生。

通过静默处理错误类型,clang 似乎不符合标准。这让我想知道 clang 还默默地做了什么。

我在 x86-64 Ubuntu 机器上使用 gcc 版本 5.4.0 和 clang 版本 3.8.0。

【问题讨论】:

  • 您可能是对的...但是如果您将malloc() 的结果转换为条件,使得第三个操作数不再是指向 void 的指针,错误就会消失。
  • 如果我是你,我会倾向于避免在我的项目的 to-C 编译器发出的 C 代码中使用 restrict。通过restrict 限定可以启用多少额外优化,任何给定编译器实际上会执行多少,以及结果可能会提高多少性能都不清楚。另一方面,通过使用restrict 限定符,您的代码承担了它必须满足的额外义务以避免未定义的行为,并且并非所有这些都可以由编译器检查。我只是看不到回报,如果有的话,证明风险是合理的。
  • @JohnBollinger,该语言非常注重性能,所以我认为性能受到影响可能是不可接受的。我已经编写了一个正式模型,该模型使用 C 的子集用于发出的代码,连同为该语言定义的约束,正式证明遵守 C 标准中限制的定义。换句话说,为程序员要求这个小规则列表允许编译器做所有其他事情来确保正确性。不过,您对额外义务的看法是正确的,这是很多工作!

标签: c compiler-errors clang c11 restrict


【解决方案1】:

是的,它看起来像一个错误。

您的问题更简单:void 可以成为restrict 合格吗?由于void 显然不是指针类型,所以答案是否定的。因为这违反了约束,编译器应该给出诊断。

我能够通过使用_Generic 表达式来欺骗clang 认罪

puts(_Generic(A ? A : malloc(8), void* : "void*"));

clang 告诉我

static.c:24:18: error: controlling expression type 'restrict void *' not compatible with any generic association type
     puts(_Generic(A ? A : malloc(8), void* : "void*"));

这表明这里的clang 确实试图匹配一个废话类型restrict void*

请向他们提交错误报告。

【讨论】:

  • 谢谢,我提交了错误报告。
【解决方案2】:

虽然编译器可以通过完全忽略限定符来满足围绕restrict 的所有义务,但想要跟踪它是什么或不允许做什么的编译器需要跟踪哪些指针保存restrict 的副本指针。给定类似的东西:

int *foo;
int *bar;
int wow(int *restrict p)
{
  foo = p;
  ...
  *p = 123;
  *foo = 456;
  *p++;
  *bar = 890;
  return *p;
}

由于foo 是从 p 派生的,编译器必须允许通过以下方式进行访问 foo 通过p 访问别名。编译器不需要做这样的考虑 对于通过bar 进行的访问,因为已知包含从p 派生的地址。

围绕restrict 的规则在指针可能或 可能不是从另一个派生的。肯定会允许编译器 在无法跟踪所有内容的情况下,只需忽略 restrict 限定符 从指针派生的指针;我不确定是否有任何此类情况 即使没有任何东西修改由 指针。如果语法结构在结构上保证调用 UB,让编译器尖叫可能比让它在一个 任意方式(尽管让编译器简单地忽略任何restrict 它无法完全处理的限定符可能更有用)。

【讨论】:

  • 感谢您的回复,但我不确定我是否理解。问题是该标准要求在违反语法的情况下提供诊断消息,其中包括使用明确禁止的类型。 Clang 不提供该消息。限制限定符的任何合规方法都不能减轻提供该消息的责任的实现。换句话说,限定符可以被实现忽略,但只有在语法正确的设置中使用;即使忽略限定符的实现也需要报告不正确的使用。
  • @Kyle:“int*”是一个对象,int* restrict 也是一个对象,短语“引用类型是对象类型的指针类型”本质上是指“不是函数指针的指针” ,而int* restrict 显然符合该要求。从malloc 返回的是void*。条件运算符用于指向对象(类型为int *restrict)和void* 的指针。我真的没有在这里看到任何语法错误。该标准缺乏术语来表达我认为作者的意图,那就是......
  • ...实现没有义务以标准规定以外的任何方式处理使用restrict 的程序,并且如果语法违规会阻止实现处理标准定义的源文件,如果restrict 对处理没有障碍,则不需要发出诊断。据我所知,该标准的作者希望 restrict 只对可能从中受益的编译器施加负担,而其他实现基本上可以忽略它。
  • 该标准要求所有语法错误都产生诊断,我在标准中没有看到任何支持他们不需要为不妨碍处理的语法错误发出诊断的内容。在我的示例中,错误是类型无效的表达式。造成这种情况的一个原因可能是两个实现可能无法就阻碍处理的因素达成一致,因此如果不需要诊断,则无法很好地定义标准合规性。例如,我可以用 Clang 编译并认为我有一个兼容的程序,但是当有人试图用 gcc 编译它时,它就不起作用了。
  • @Kyle:我想我看到了这个问题。 6.5.15 说? :operator 必须产生一个“适当地-qualified”指针,但我不知道有什么表明restrict 在这种情况下会被继承,特别是考虑到将restrict 应用于void* 的目标将是in合适的。
猜你喜欢
  • 2011-10-31
  • 2013-12-17
  • 2018-07-24
  • 2012-10-28
  • 1970-01-01
  • 2012-05-22
  • 2011-09-08
  • 2017-10-24
  • 2019-10-31
相关资源
最近更新 更多