GCC 对可能有效的代码抛出 init-list-lifetime 警告?

Posted

技术标签:

【中文标题】GCC 对可能有效的代码抛出 init-list-lifetime 警告?【英文标题】:GCC throws init-list-lifetime warning on potentially valid code? 【发布时间】:2020-11-02 17:43:35 【问题描述】:

我在 Debian 不稳定的 GCC 9.3.0 上运行。

我从事的一个项目最近发生了变化,引入了类似于下面的代码。

#include <initializer_list>
#include <map>
#include <vector>

std::map<int, std::vector<int>> ex = []
    /* for reused lists */
    std::initializer_list<int> module_options;

    return (decltype(ex)) 
        1, module_options = 
            1, 2, 3
        ,
        2, module_options,
    ;
();

这个想法是初始化列表的相同子部分首先在顶部声明,在第一次使用时定义并分配给std:initializer_list变量,然后在多个地方使用。这很方便,有些人可能会认为更具可读性,这就是它被接受的原因。

一切都很好,直到几天前 GCC 开始在代码上抛出 init-list-lifetime 警告。我们在回归中使用-Werror,所以这对我来说失败了。我还尝试使用 clang 9.0.1 进行编译,它不会引发警告。

<source>: In lambda function:
<source>:12:9: warning: assignment from temporary 'initializer_list' does not extend the lifetime of the underlying array [-Winit-list-lifetime]
   12 |         ,
      |         ^

根据cppreference:

在原始初始化列表对象的生命周期结束后,不能保证底层数组存在。 std::initializer_list 的存储是未指定的(即它可以是自动、临时或静态只读内存,具体取决于具体情况)。

所以我的理解是,在包含初始化程序列表的范围内定义的通用初始化程序列表值具有以封闭初始化程序列表结束的生命周期。从前面的 cppreference 页面中,它提到 std::initializer_list 是一个“轻量级代理对象”,这意味着它不获取临时对象的所有权或延长它的生命周期。这意味着底层数组不能保证在以后的使用中存在,这就是抛出警告的原因。这个分析正确吗?

我可以通过将std::initializer_list 变量初始化移动到声明中来防止出现警告。有关项目中问题的完整详细信息,请参阅PR。

【问题讨论】:

@FrançoisAndrieux 为问题添加了一个最小示例,您可以看到它here。 std::initializer_list 并不是为了坚持而设计的。改用std::vector 可以避免这些问题。您的 MCVE 使初始化数据看起来是恒定的。在这种情况下,您可以使用constexpr 数组,这将帮助编译器生成它可以生成的最佳代码。 我也不确定是否定义了初始化列表中元素的评估顺序。您应该在大型初始化程序列表之外定义数据的值。即使订单定义明确并且您的方法有效,它也不必要地试图将过多的工作打包到过少的行中。对这段代码进行推理是不必要的困难。 @FrançoisAndrieux The order of evaluation is left-to-right in an initializer list.. @FrançoisAndrieux 因此,您的建议是将通用声明提升到初始化列表声明之上,并使用std::vectorconstexpr int[] 来保存值?这与链接 PR 的作用类似。 【参考方案1】:

所以我的理解是,在包含初始化程序列表的范围内定义的通用初始化程序列表值具有以封闭初始化程序列表结束的生命周期

你说的是prvalue表达式1, 2, 3创建的对象,对吧?

decl.init.list/6中有一个例子,

该数组与任何其他临时对象 ([class.temporary]) 具有相同的生命周期,除了从数组初始化 initializer_­list 对象会延长数组的生命周期,就像将引用绑定到临时对象一样。 [例子:

// ...
std::initializer_list<int> i3 =  1, 2, 3 ;
// ...

标准(或草案)所说的

对于i3initializer_­list 对象是一个变量,因此数组在变量的生命周期内保持不变。

这表明对象必须被物化并且应该延长它的生命周期。

但是,您没有从表达式初始化initializer_list 对象,因为您的变量已经初始化。如果我们将您的代码重写为对概念的调用

module_options.operator=(1, 2, 3)

那么我们不会期望临时生命周期延长到函数调用结束之后。

我曾怀疑这个临时对象仍然会存活到完整表达式的末尾,因为我认为绑定一个引用应该延长它的生命周期而不是缩短它:但不可否认@987654323 @ 说 "... 在引用的生命周期内持续存在 ..." 而不是 "... 在 至少 生命周期内持续存在..."

但是,这确实意味着您的原始代码的以下变体应该可以满足您的需求:

std::map<int, std::vector<int>> ex = []
    /* for reused lists */
    std::initializer_list<int> module_options  1, 2, 3 ;

    return (decltype(ex)) 
        1, module_options,
        2, module_options,
    ;
();

【讨论】:

“我们不希望临时生命周期延长到函数调用结束之后。我曾怀疑临时生命周期仍然会活到完整表达式的末尾” i> - 难道 all 临时对象的生命周期不能保证与其完整封闭表达式的生命周期相匹配吗?这会使我引用的第一句话是错误的。

以上是关于GCC 对可能有效的代码抛出 init-list-lifetime 警告?的主要内容,如果未能解决你的问题,请参考以下文章

减法和检测下溢,最有效的方法? (带有 GCC 的 x86/64)

gcc inline asm 跳转到带有交叉抛出异常的标签

gcc 添加include环境变量和lib环境变量(转载)

Laravel / Lumen formRequest 在提交所有有效值时抛出错误

MAC OS X 10.8 上的 gcc 4.8 抛出“架构 x86_64 的未定义符号:”

为啥以下代码在在线 ide(gcc 7.2.0) 上有效,但在 ubuntu 上出错?