【问题标题】:Why isn't array of const structs placed in .rodata when it references a const struct by name?当 .rodata 按名称引用 const 结构时,为什么不将 const 结构数组放置在 .rodata 中?
【发布时间】:2016-08-15 20:03:49
【问题描述】:

我有以下结构排列:

typedef struct { 
    int a;       
} Foo;

const Foo END = {0};   

const Foo table_1[] = {
    {2}, {0}           
};                     

const Foo table_2[] = {         
    {2}, END                    
};                              

基本上,我有一个结构,以及该结构的几个数组。现在,这些数组的内容永远不会改变:它们是一些在运行时使用的查找表,因此,在嵌入式环境中,我希望这些数据驻留在 ROM 中(我是 very RAM-受限,实际上是为每几个字节而战)。可以想象,没有什么可以阻止所有这些表驻留在 ROM 中(一切都是 const)。

实际上,table_1.rodata 结尾(因此在启动期间它不会被复制到 RAM 中)和 table_2 - 在 .data 中(同时消耗 ROM 和 RAM)。反汇编目标文件时,我还可以看到一些代码,初始化table_2

我希望所有上述数组都以.rodata 结尾,但似乎只有当我“完整”写出结构初始化时才会发生(抱歉,不确定,正确的术语是什么) .

这些初始化有什么区别?因为只是初始化不同 - 类型相同,实际数据也相同。是否正在进行某种优化(想知道这里优化了什么)?有没有办法禁用它?我的意思是,我可以 #define 离开所有常见的表成员并完成它,但这似乎是一个 hack,此外,我真的很想了解这里发生了什么。

我正在使用 gcc-arm-none-eabi 工具链,使用 -Os 构建,gcc 版本是 4.8.1。

【问题讨论】:

  • 看起来编译器无法优化子表达式不是文字的情况,即使它是const 表达式。
  • 您使用的是 C 还是 C++?也许使用 C++ 你可以使用constexpr
  • 为什么要禁用优化?优化就是在第一个示例中将其放入 .rodata 中,这似乎是您想要它做的事情。
  • 尝试在定义(实现)前加上static
  • @ThomasMatthews 啊,不错,实际代码中定义为static。不过这没什么区别。

标签: c++ arrays gcc struct


【解决方案1】:

gcc 5.1 及更高版本执行此优化;它不是强制性的,但在 [basic.start.init] 中由 C++ 标准考虑:

3 - 允许实现使用静态存储执行非局部变量的初始化 持续时间作为静态初始化,即使这种初始化不需要静态完成,前提是 那个

  • 动态版本的初始化不会改变任何其他命名空间对象的值 初始化之前的范围,以及
  • 初始化的静态版本在初始化变量中产生的值与将产生的值相同 如果不需要静态初始化的所有变量都是由动态初始化产生的 动态初始化。

如果你的 gcc 版本支持constexpr,那么标记END constexpr 应该足以让它静态初始化table_2;您也可以标记table_2 constexpr 以确保(同上):

2 - 执行常量初始化:[...]

  • 如果 [...] 每个出现在其初始化程序中的完整表达式都是一个常量表达式。

为什么我们需要constexpr - 为什么const 不够?这是因为const 对象仍然可以有mutable 成员(可能是成员的成员等),这将允许它在END 的初始化和table_2 的初始化之间进行更改:

struct Bar { mutable int a; };
const Bar END = {0};
int unused = ++END.a; // !!
struct Foo { int a; };
const Foo table_2[] = { {2}, {END.a} };

constexpr 通常会阻止这种情况,因为 [expr.const]/2 确保具有mutable 成员的复合对象不能用于constexpr 对象的初始化。 constexpr 对象仍然可以拥有自己的 mutable 成员,但这会阻止它被用于初始化另一个 constexpr 对象。

【讨论】:

  • Re你的撤回编辑,这可能是一个愚蠢的问题,但幽默我......它是否定义为声明为const一个具有可变成员的对象的实例,然后调用变异的方法说会员?还是仅在对象最初声明为非const 时才有效,这意味着只有间接引用或指针可以是const-qualified?我一直认为是后者,直到现在我什至从未考虑过前者——我觉得它可以走任何一条路。
  • @underscore_d 绝对是前者;具有mutable 成员的对象可以修改该成员,即使整个对象声明为const(甚至是constexpr)。
  • 谢谢!这确实更有意义。我只是还不需要mutable(无论好坏!),只有const_cast,并且在看到你的编辑之前没有有意识地考虑过mutable - 所以两者混淆了,让我感到困惑,呵呵.允许 constexpr 的含义是什么?例如如果对象的构造函数修改了成员,这意味着在另一个 constexpr 可以从中分配时它是不同的——这一切都是在编译时考虑的吗?请随时将我发送到一个很好的链接!
  • @underscore_d 想不出任何全面的文章,尽管 isocpp 常见问题解答非常好:isocpp.org/wiki/faq/cpp11-language#cpp11-constexpr mutable 成员阻止对象 - 甚至是 constexpr 对象 - 被使用(如一个整体)在一个常量表达式中。 constexpr 构造函数可以在 constexpr 对象上设置成员 - 这个想法是构造函数足够“简单”以在编译时运行,因此编译器知道对象的确切内容并且可以(如果需要)放置它在rodata段中。
【解决方案2】:

假设 C++。

table_2 的初始化不是 C++ 标准中的常量表达式。常量表达式是指没有(除其他外)左值到右值转换(使用END 时有)的所有内容,除非左值(END):

  1. 具有整数或枚举类型,指的是完整的非易失性 const 对象,用常量表达式初始化。

END 既不是整数也不是枚举。

  1. 是一个非易失性泛左值,它引用字符串的一个元素 字面意思

END 不是字符串文字。

  1. 具有字面量类型并引用使用 constexpr 定义的非易失性对象或其非可变子对象

END 没有用 constexpr 定义。

  1. 具有字面量类型并引用一个非易失性对象,其生命周期在此表达式的计算中开始

END 的生命周期未在 table_2 的评估范围内开始。

所以编译器别无选择,只能将 table_2 初始化从静态上下文中移开。如果你用constexpr 声明END,我认为条件3 会得到满足,你会在rodata 中得到很好的结果。

来自 C++ 标准的相关文本位于 [expr.const] 部分,第 2.7.2 段。

【讨论】:

  • 完全没有问题 - 只是认为将它集成到一个已经很好的答案中会更好,这意味着我们现在可以清理这些 cmets!糟糕 - 你引用的是 C++14 吗?我的 PDF 草稿在另一个驱动器上...
  • 我通常从isocpp.org/std/the-standard 下载它们,虽然从技术上讲它们是第二最新版本。
  • “编译器别无选择”——基于as-if规则,由于静态初始化不会改变行为,编译器确实有选择。
猜你喜欢
  • 1970-01-01
  • 2017-09-11
  • 1970-01-01
  • 1970-01-01
  • 2016-12-23
  • 2020-10-30
  • 1970-01-01
  • 2014-08-08
  • 2011-06-03
相关资源
最近更新 更多