【问题标题】:Is the GCC option -Wdeclaration-after-statement purely stylistic?GCC 选项 -Wdeclaration-after-statement 是纯粹的风格吗?
【发布时间】:2021-12-14 06:30:29
【问题描述】:

-Wdeclaration-after-statement 选项是否只有风格?我的意思是,如果我在我的 C 代码中对定义了变量的所有情况进行宏处理,并以从旧样式 C90 迁移到新 C99 样式的相同方式在它们中初始化它们,那么该代码是否是字节码? -byte 一样吗?

以下是选项 -Wdeclaration-after-statement 的记录方式(来自 man gcc):

在块中的语句之后发现声明时发出警告。这个结构源自 C++,是随 ISO C99 引入的,默认情况下在 GCC 中是允许的。 ISO C90 不支持它。

它允许你使用类似的代码

int a;
{
    a = 42;
    printf( "%d", a );
}

把它变成

int a = 42;
printf( "%d", a );

这是follow-up to my question here

【问题讨论】:

  • 不,这不是纯粹的风格。如果您编写的代码也必须在 C90 编译器上构建,那么当您编写其他编译器不支持的代码时,您会希望收到警告。
  • @NateEldredge 已更新至清晰——这不是我的意思。我也在下面自己回答了这个问题。

标签: c gcc c89


【解决方案1】:

我可能在这里感到困惑,但我认为我们遗漏了一些东西。

在 C99 之前,所有变量声明都必须发生在块中的任何语句之前。给它赋值的位置无关紧要(可能在生成的汇编代码中除外)。

int a;
do_something();
a = 7;
do_something_else();

您在 C 中不能做但在 C++ 中始终完全合法的是混合声明和语句:

do_something();
int a = 7;
do_something_else();

随着 C99 的出现,您现在可以在 C 中执行与在 C++ 中相同的操作,并在块中的任何位置声明变量,即使在非声明语句之后也是如此。

最终,这是一个设计决策,基于使编写泄漏到语言规范中的编译器更容易。编译器现在更复杂了(而且更大了)。

【讨论】:

  • 问题是,如果在你上面的例子中你宏化了代码,使得在块中的语句之前出现的 all 声明被它们的初始化器连接起来,这些初始化器在 C90 中出现在它们下面代码编译是否相同。答案似乎是否定的,它不会,也不可能以一起声明和初始化变量的方式重写违反 -Wdeclaration-after-statement 的代码。
  • 啊,是的。这是正确的,因为它会破坏编译器推理和生成保证在所有情况下都能正常工作的代码的能力。
【解决方案2】:

在这种情况下的语句要么是指以; 结尾的完整表达式,要么是一个复合语句{ }。这些术语取自正式的 C 语法。

声明 after 声明不适用于您问题中的代码。因为...您将声明放在(复合)语句之前。您的两个示例与此 gcc 设置无关,因此您似乎误解了它的作用。

相反,相关的例子是:

{
    int a = 42;
}

{
  puts("hello");
  int a = 42;
}

前一个例子适用于任何 C 版本。后者在标准 C 中很好,但在已弃用的 C90 标准中不行。因此,如今警告的唯一目的是为强制执行某种编码风格的标准 C 程序提供诊断信息。

绝大多数程序员不应使用此警告,而应遵守 ISO 9899:2018 中定义的标准 C。


编辑:

这只是关于您是否可以将代码从 C90 重写为 C99 约定并获得二进制兼容性

是的,您可以重写为 C99 样式而不会产生任何后果。这些都不会影响变量在 C 所称的“抽象机器”中的处理方式。以下是一些示例,2 个相关,1 个不相关:

void relevant_c99_example (void) {
    if ( 1 ) {
        int a = 42;
        printf( "%d", a );
    }
    else {
        int a = 42;
        printf( "%d", a );
    }
}

void relevant_c90_example (void) {
    int a = 42;
    if ( 1 ) {
        printf( "%d", a );
    }
    else {
        printf( "%d", a );
    }
}

void irrelevant_example (void) {
    int a;
    if ( 1 ) {
        a = 42;
        printf( "%d", a );
    }
    else {
        a = 42;
        printf( "%d", a );
    }
}

https://godbolt.org/z/1oqvoesx7

在 x86_64 的 gcc clang 上测试时,这 3 个生成 100% 相同的机器代码。当优化被禁用时,它们甚至会产生 100% 相同的机器代码!不应该期待哪一个,因为这不是您正确地对 C 代码进行基准测试的方式。但这里恰好是这种情况。

【讨论】:

    【解决方案3】:

    不,不是。例如,上面的两个 sn-ps 将逐字节编译相同。下面的foobar 也会如此,baz 不会。这是link to GodBolt这表明将初始化提升到声明可能不会产生相同的代码

    void foo () {
        int a;
        {
            if ( 1 ) {
                a = 42;
                printf( "%d", a );
            }
            else {
                a = 42;
                printf( "%d", a );
            }
        }
    }
    
    void bar () {
        int a = 42;
        {
            if ( 1 ) {
                printf( "%d", a );
            }
            else {
                printf( "%d", a );
            }
        }
    }
    
    void baz () {
        int a;
        {
            if ( rand() > 0 ) {
                a = 42;
                printf( "%d", a );
            }
            else {
                a = 42;
                printf( "%d", a );
            }
        }
    }
    

    【讨论】:

    • 您误解了此警告的作用。这没有回答问题。
    • @Lundin 这是我的问题。确实如此。这表明我无法对写入-Wdeclaration-after-statement 下的代码进行宏修复,然后删除-Wdeclaration-after-statement
    • 不,不是因为此代码与警告完全无关,问题中的代码也没有。请参阅我发布的答案。同样由于某种原因,我显示正确 Godbolt 代码的评论已被删除。
    • @Lundin 我的问题不是如何解决警告。我知道警告是什么。我想知道我是否可以禁用警告 并自动 将代码库迁移到具有初始化声明的现代 C 约定。答案是编译后的输出可能会有所不同,这就是我正在寻找的答案。
    • 这里的这些例子证明不了,因为它们没有任何区别。您的 golbolt 示例无关紧要,因为它显示了未优化的代码。如果您实际上正确编译了godbolt.org/z/qWYWvznMn,那么这三个版本之间的唯一区别就是对rand() 的调用,您在变量赋值之后对其进行了排序。在所有版本中,分支都不存在于机器代码中,并且在所有版本中,变量都存储在寄存器mov esi, 42中。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-08-14
    • 1970-01-01
    • 2011-04-18
    • 2014-02-16
    • 1970-01-01
    • 2016-10-28
    • 1970-01-01
    相关资源
    最近更新 更多