导致非标准行为的 #pragma 是不是会导致 __STDC__ 宏未定义为 1?

Posted

技术标签:

【中文标题】导致非标准行为的 #pragma 是不是会导致 __STDC__ 宏未定义为 1?【英文标题】:Shall a #pragma leading to nonstandard behavior cause __STDC__ macro not to be defined to 1?导致非标准行为的 #pragma 是否会导致 __STDC__ 宏未定义为 1? 【发布时间】:2021-07-12 04:31:31 【问题描述】:

简单问题:导致非标准行为的#pragma 是否会导致__STDC__ 宏未定义为1? (C 标准是否明确规定了这一点?如果是,那么在哪个部分?如果不是,那为什么?) 问题原因:见下文。

示例代码(t28.c):

#pragma warning( disable : 34 )
typedef int T[];

int main()

    int rc = sizeof(T);
#if __STDC__ == 1
    rc = 0;
#else
    rc = 1;
#endif
    return rc;

调用:cl t28.c /std:c11 /Za && t28 ; echo $?

预期结果:1

实际结果:0

编译器版本:

cl
Microsoft (R) C/C++ Optimizing Compiler Version 19.28.29913 for x64

注意:C11(6.5.3.4 sizeof 和 _Alignof 运算符)(已添加重点):

sizeof 运算符不应应用于具有函数类型或不完整类型的表达式,...

这里我们看到#pragma 导致非标准行为:违反了“shall 要求”,未生成诊断消息,调用编译器的后端,生成并成功执行了.exe。但是,这种非标准行为不会导致 __STDC__ 宏未定义为 1

问题的原因:测试。一项类似于t28.c 的测试失败,因为它需要返回码1__STDC__ 未定义为1)。系统的哪个部分包含错误:测试或编译器(或两者)?

【问题讨论】:

(a) #pragma 后面没有STDC 导致实现以实现定义的方式运行。这可能包括更改__STDC__ 的行为,但在这种情况下可能不会。但进一步回答需要说明此编译指示的实施文档。 (b) 在这个 C 实现中,__STDC__ 通常用什么替换(即没有#pragma 并且没有代码导致编译警告或错误)? @thebusybee:这与__STDC__ 的问题有什么关系? 为什么你的问题会问到__STDC__ 没有被定义为 1 而结果表明它是 1?你的帖子说程序的实际结果是0,这意味着#if的“then”部分被使用了,这意味着__STDC__ == 1是真的。 “一致的实现”是实现的属性,而不是您的代码。杂注或其他任何内容的存在不会改变实现的一致性。 @pmor:编译指示和命令行开关是不同的东西。为了评估一致性,每个单独的开关组合与编译器都被认为是不同的 C 实现。正在编译的源代码中存在编译指示不会使编译器成为不同的 C 实现。 【参考方案1】:

这里我们看到#pragma 导致非标准行为:违反了“shall 要求”,没有生成诊断消息,调用编译器的后端,生成并成功执行了.exe。但是,这种非标准行为不会导致 __STDC__ 宏未定义为 1。

首先,__STDC__ 宏并非旨在成为报告“在此编译期间发生的导致非标准行为的某些事情”的机制。它不是以那种方式动态(变化)的。对__STDC__ 的渴望只是为了报告C 实现符合要求。 (这只是一个愿望,因为 C 标准可以要求符合标准的实现将 __STDC__ 定义为 1,但无法控制不符合标准的实现将其定义为。)如果实现符合标准,__STDC__ 将始终为 1任何特定编译中发生的事情。

(请注意,一个编译器可能包含由各种命令行开关选择的多个 C 实现,例如为各种类型请求不同的大小、启用或禁用符合标准的扩展、启用来自 C 标准的不符合标准的变体,等等。某些开关组合可能会导致 __STDC__ 被定义为 1,而另一些则不会。)

其次,#pragma warning( disable : 34 ) 不会导致非标准行为。根据 C 2018 6.10.6 1,该指令“导致实现以实现定义的方式运行。该行为可能会导致翻译失败或导致翻译器或生成的程序以 [anotherly] 不符合的方式运行。”因此,假设实现记录了 pragma 以抑制有关违反 6.5.3.4 1 中关于 sizeof 运算符的约束的警告,那么这是 C 标准允许的。 6.10.6 1 中的这条规则覆盖了 6.5.3.4 1 中的约束。该行为是 C 标准允许的,因此符合要求。

【讨论】:

【参考方案2】:

标准保留 #pragma STDC ... 用于未来的语言扩展 - 所有其他 pragma 都是实现定义的 (C17 6.10.6)。

不要混淆哪个__STDC__,它设置为1 以将编译器标记为符合标准的实现 (C17 6.10.8.1)。

该术语依次在 C17 4/5 和 4/6 中定义:

严格遵守的程序应仅使用本国际标准中指定的语言和库的那些特性

两种形式的一致的实现是托管的和独立的。一个符合的 托管实施应接受任何严格遵守的程序。 //-/ 一个符合标准的实现可以有扩展(包括额外的库函数),只要它们不改变任何严格符合标准的程序的行为。

这并不意味着只要应用程序是严格符合标准的程序就设置__STDC__,而是如果编译器及其当前选项是符合标准的实现,则将其设置为固定为 1 或 0。


例如,编译器可能包含在标准头文件中转储非标准标识符的 POSIX 库。如果这些标识符通过命名空间冲突影响严格符合程序的行为,则不应将 __STDC__ 设置为 1。一个不符合的示例是当我使用 gcc -std=gnu17 编译它时:

#include <string.h>
#if __STDC__== 1
int strdup; // file scope declaration
#endif

我收到“错误:'strdup' 被重新声明为不同类型的符号”。这是不合规的行为。可以说,__STDC__-std=gnu17 下运行时设置为 1 是一个错误,因为这不是一个符合要求的实现。

如果我切换到-std=c17,它会按照应有的方式干净地编译 - 然后 gcc 是一个符合要求的实现。


否则,正如我们从上面引用的部分可以看出,符合标准的实现仍然可以有扩展。

在您的具体情况下,您选择禁用诊断作为非标准扩展。这是程序员的调用,而不是使编译器不兼容的东西。它只是使 应用程序 不符合标准,但编译器仍然可以编译一个严格符合标准的程序,即使你没有提供它。

如果您想展示 VS 的不合格之处,那么这是一个更好的程序:

#include <stdio.h>

int main()

  int x = __STDC__;
  #if defined(__STDC_NO_VLA__) && __STDC_NO_VLA__==1
    int arr [2];
  #else
    int arr [x];
  #endif

  printf("%zu", sizeof arr / sizeof *arr);

如果支持 VLA,则符合标准的实现应打印 1,否则为 2。损坏的实现将给出大量奇怪的诊断信息。

【讨论】:

感谢您的详细回答。仅供参考:cl t318.c /std:c11 /Za &amp;&amp; t318.exe 不会产生错误、警告并打印 2。版本:19.28.29913。 仍然不明白:为什么#pragma 导致非标准行为不会导致实现不符合?在示例中,符合t28.c 的实现需要产生诊断消息,但是,由于#pragma 不会产生这样的诊断消息,但是,__STDC__ 仍然是 1。

以上是关于导致非标准行为的 #pragma 是不是会导致 __STDC__ 宏未定义为 1?的主要内容,如果未能解决你的问题,请参考以下文章

以下链式赋值是不是会导致未定义的行为?

_packed / #pragma pack() 字节对齐问题

在哪些情况下 std::optional operator == 会导致未定义的行为?

循环和便利方法是不是会导致 ARC 出现内存峰值?

#pragma once vs #ifndef

可能会导致.NET内存泄露的8种行为