当前位置: 首页 > 知识库问答 >
问题:

片段在Coliru中编译时带有警告,但在Ideone中正常编译。哪个是正确的?

锺离正祥
2023-03-14

该代码在Coliru中编译,带有警告[Unitized membersa[1]。i和[2]。标准::cout中的i

#include <iostream>

struct A
{
    int i;
    A(int j) : i{j} {};
    A() = default;
};

int main() {
    A a[3] = { A(1) };
    std::cout << a[1].i << ' ' << a[2].i << '\n';
}

根据我对iso§8.5 p7的解释,Ideone是正确的,因为本条款中有第4个要点。

这是N3797的§8.5 p7

初始化T型对象的值意味着:

  • 如果T是一个(可能是cv限定的)类类型(第9条),没有默认构造函数(12.1)或用户提供或删除的默认构造函数,则对象默认初始化

被值初始化的对象被视为已构造,因此受本国际标准适用于“构造”对象、“构造函数已完成的对象”等的规定的约束,即使没有调用构造函数进行对象的初始化。


共有3个答案

鲁财
2023-03-14

Coliru的默认编译命令为:

g++-4.8 -std=c++11 -O2 -Wall -pedantic -pthread main.cpp && ./a.out

它使用了墙,可以提供更广泛的警告。为了显得迂腐,它启用了以下警告:

  -Waddress   
  -Warray-bounds (only with -O2)  
  -Wc++11-compat  
  -Wchar-subscripts  
  -Wenum-compare (in C/ObjC; this is on by default in C++) 
  -Wimplicit-int (C and Objective-C only) 
  -Wimplicit-function-declaration (C and Objective-C only) 
  -Wcomment  
  -Wformat   
  -Wmain (only for C/ObjC and unless -ffreestanding)  
  -Wmaybe-uninitialized 
  -Wmissing-braces (only for C/ObjC) 
  -Wnonnull  
  -Wopenmp-simd 
  -Wparentheses  
  -Wpointer-sign  
  -Wreorder   
  -Wreturn-type  
  -Wsequence-point  
  -Wsign-compare (only in C++)  
  -Wstrict-aliasing  
  -Wstrict-overflow=1  
  -Wswitch  
  -Wtrigraphs  
  -Wuninitialized  
  -Wunknown-pragmas  
  -Wunused-function  
  -Wunused-label     
  -Wunused-value     
  -Wunused-variable  
  -Wvolatile-register-var 

更多信息可以在这里找到。

尽管Ideone也使用GCC 4.8,但它很可能没有设置-Wall。如您所见,如果没有该标志,Coliru上也不会出现警告。

明确地说:它们实际上是同一个编译器,具有相同的标志,它们的行为完全相同,因此它们都是正确的。

白博赡
2023-03-14

对于同时具有默认构造函数和另一个非默认构造函数的类,C 11和N3797之间的值初始化行为存在明显差异。C 11§8.5/7:

初始化T型对象的值意味着:

  • 如果T是一个(可能是cv限定的)类类型(第9条),具有用户提供的构造函数(12.1),则调用T的默认构造函数(如果T没有可访问的默认构造函数,则初始化是病态的)

N3797§8.5/8:

初始化T型对象的值意味着:

  • 如果T是一个(可能是cv限定的)类类型(第9条),没有默认构造函数(12.1)或用户提供或删除的默认构造函数,则该对象是默认初始化的;
  • 如果T是一个(可能是cv限定的)类类型,没有用户提供或删除的默认构造函数,则对象是零初始化的,并检查默认初始化的语义约束,如果T有一个非平凡的默认构造函数,对象是默认初始化的;
  • 如果T是数组类型,则每个元素都是值初始化的;
  • 否则,对象是零初始化的。

您的结构A有一个用户声明的默认构造函数 和用户提供的非默认构造函数a(intj):i{j}{}。在C 11中,它受制于第一个项目符号:它有一个用户提供的构造函数,因此调用默认构造函数(它什么都不做:a的默认构造函数很小)。在N3797中,第二个项目符号适用,因为A“没有用户提供或删除的默认构造函数”,因此对象初始化为零。

简单地说,在C 11中,具有任何用户提供的构造函数的类的对象的值初始化在默认初始化之前不会执行零初始化。在N3797中,没有用户提供的默认构造函数的类的对象的值初始化将在默认初始化之前执行零初始化。

Coliru上的clang版本似乎一直在跟踪C 11之后的标准,但GCC 4.8没有。

编辑:这个测试程序证明GCC 4.8实际上确实遵循N3797的值初始化规则。问题似乎是默认初始化没有提供初始化器的数组元素,而不是按照标准要求对它们进行值初始化。请注意第二个数组元素之间的行为差异,它显式提供了一个空初始化器,而第三个没有提供初始化器。

这看起来像是一个可能的GCC错误。

编辑:由同一GCC版本在Ideone上编译的同一个测试程序没有显示错误。不知道这里发生了什么。可能不同的编译器标志会影响Ideone上的输出,我不知道如何确定使用的编译器命令行。

丌官炎彬
2023-03-14
匿名用户

就C 14(N3797)而言,Ideone是正确的(参见Casey对C 11的回答),因为a[1]和a[2]是用a{}初始化的,这是值初始化,导致i为0。这来自N3797§8.5.1/7:

如果列表中的初始化子句少于集合中的成员,则每个未显式初始化的成员都应从其大括号或相等初始化器初始化,或者,如果没有大括号或相等初始化器,则从空初始化器列表(8.5.4)。[示例:

struct S { int a; const char* b; int c; int d = b[a]; };
S ss = { 1, "asdf" };

使用1初始化ss. a,使用“asdf”初始化ss. b,使用int{}(即0)形式表达式的值初始化ss. c,使用ss. b[ss. a]的值初始化ss. d(即's')

数组是§8.5.1/1中的聚合(聚合是数组...),因此这适用于数组的初始化。

表达式T{}(一个空的初始化列表)根据§8.5.4初始化对象 /3:

否则,如果初始化列表没有元素并且T是具有默认构造函数的类类型,则对象是值初始化的。

我们可以确认,值初始化使i的值为0,§8.5/8:

如果T是一个(可能是cv限定的)类类型,没有用户提供或删除的默认构造函数,则该对象为零初始化,并检查默认初始化的语义约束,如果T有一个非平凡的默认构造函数,则该对象为默认初始化;

根据§8.4.2/4,您的类没有用户提供的默认构造函数:

如果函数是用户声明的并且在其第一个声明中没有显式默认或删除,则该函数是用户提供的。

值得注意的一点是,如果默认构造函数是用户提供的,并且没有初始化,则将使用8.5/8的第一个点,并且将保持未初始化状态,如本例中所示。

最后,对比较进行了小说明。Ideone使用了几个不同版本的GCC。使用哪个版本会有所不同(如果需要,可以使用版本检查)。在这种情况下,编译器标志也略有不同。如果存在-std=c 1y(我不知道除了使用添加的功能之外还有什么方法来检查),将有一些c 14支持,但不是完全支持,因此可能无法实现小的更改(t{}t(),从大括号或同等初始值设定项初始化,并检查默认初始化的语义约束)。事实上,你甚至可以先检查一下。Coliru允许您配置build命令,因此只说Coliru是非常模糊的。

不管怎样,在提供足够的C 14支持之前(或者至少在标准化之前),使用N3797测试一致性行为都不太值得。在这之前,我倾向于坚持使用N3485。在这个具体的例子中,我认为这两个标准的行为没有任何区别 请查看凯西的答案,了解这两个标准在这一问题上的不同之处。您有一个转换构造函数,因此您的对象将默认在C 11中初始化。

 类似资料:
  • 问题内容: 我正在尝试使用JavaMail API发送电子邮件。我从自解压二进制文件在我的主目录中安装了jdk 1.5。我正在使用Ubintu 9.10 我使用下一条命令编译程序: 〜/ jdk1.5.0_22 / bin / javac -classpath〜/ jdk1.5.0_22 / jre / lib / javamail-1.4.3 / mail.jar:〜/ jdk1.5.0_22

  • 我下载了cratedb的源代码,但无法正确编译,下面是详细说明:jdk:1.8 /workspace/crate编译 应该是elasticsearch包不见了,下一步怎么办? 谢谢!

  • 考虑以下简单类: 默认的模板化set方法对大多数T类型的值都很好,但我需要对特定类型的行为进行特殊化,将其称为String。 因此,在关联的CPP文件中,我添加了以下内容: 为什么在Xcode中工作而在Visual Studio中不工作 哪个编译器的行为正确,该问题应该被认为是另一个编译器中的bug吗? 顺便说一句,我尝试在头文件中创建一个显式签名: Xcode对此没有抱怨,一切都仍然有效。然而,

  • 问题内容: 我在主软件包的一个目录下有一些文件: main.go config.go server.go 当我这样做时:“执行构建”程序将完美构建并运行良好。当我这样做时:“ go run main.go”失败了。 输出: 未定义的符号是结构,并且大写,因此应将其导出。 我的Go版本:go1.1.2 linux / amd64 问题答案: 这应该工作 Go run需要一个文件或多个文件,并且它仅合

  • 问题内容: 所以我在IntelliJ中有一个Maven模块(模块A)。最近,我将一些类从其中移到了另一个新的Maven模块(模块B)中,并为其添加了依赖项。完成此操作后,我还修改了其中一个移动类(现在位于模块B中)的方法的签名。 我重新导入了poms,以便IntelliJ拾取依赖项更改,并确保受影响的文件的所有Java导入都再次正确。现在,当我尝试运行我的webapp(取决于两个模块)时,在模块A

  • 忽略编译器警告 格式 #pragma clang push #pragma clang diagnostic ignored "错误类型" // 存在警告的代码 #pragma clang pop > 1.在需要忽略的警告处右键 -- Reveal in Log 2.会给出警告的详细信息,其中包括警告的类型 3.高亮选中的便是这个警告的类型 4.在警告代码处增加如下代码 Command