为什么我可以在main()之前通过std :: map填充变量?

时间:2014-12-27 02:49:20

标签: c++ c++11 initialization undefined-behavior stdmap

我在一个旧程序中偶然发现了一些奇怪的行为,并弄清楚为什么G ++和CLang ++允许它发生。我在main()之前声明并初始化了一些全局变量。奇怪的是它们是通过静态std :: map初始化的,它使用下标运算符同时填充。一旦main()运行,一切似乎都在正确的位置,地图的大小显示正确的填充项数以及包含main()之前显示的值的变量。

#include <map>
#include <iostream>

static std::map<int, const char*> staticMap;

const char* const a = staticMap[0] = []()->const char* {return "a";}();
const char* const b = staticMap[1] = []()->const char* {return "b";}();
const char* const c = staticMap[2] = []()->const char* {return "c";}();
const char* const d = staticMap[3] = []()->const char* {return "d";}();
const char* const e = staticMap[4] = []()->const char* {return "e";}();

int main() {
    std::cout << "# Items: " << staticMap.size() << '\n' << std::endl;

    std::cout << "Values:\n";
    std::cout << "\"a\" = " << a << '\n';
    std::cout << "\"b\" = " << b << '\n';
    std::cout << "\"c\" = " << c << '\n';
    std::cout << "\"d\" = " << d << '\n';
    std::cout << "\"e\" = " << e << '\n';

    std::cout << std::endl;

    std::cout << "Map Contents:" << std::endl;;
    for (unsigned i = 0; i < 5; ++i) {
      std::cout << "\t" << staticMap[i] << std::endl;
    }

    return 0;
}

以下是尝试G ++和CLang后的结果(我使用了标志-std = c ++ 11 -Wall -Werror -Wextra -pedantic-errors):

# Items: 5
Values:
"a" = a
"b" = b
"c" = c
"d" = d
"e" = e
Map Contents:
    a
    b
    c
    d
    e

这是C ++本身允许的内容吗?我甚至创建了自己的地图类型并得到了相同的结果,但我仍然不确定它是否可以依赖它。

3 个答案:

答案 0 :(得分:9)

这是完全正确的代码,虽然有点不寻常。该标准保证同一TU中的全局变量按声明顺序初始化(因此staticMap将在其他初始化发生之前构造),并且在初始化表达式中包含对重载运算符的调用并不奇怪。

实际上,所有lambdas的东西都是矫枉过正的,你可以simply do

const char* a = staticMap[0] = "a";
const char* b = staticMap[1] = "b";
const char* c = staticMap[2] = "c";
const char* d = staticMap[3] = "d";
const char* e = staticMap[4] = "e";

或者,even simpler

const char *dummy = (
    staticMap[0]="a",
    staticMap[1]="b",
    staticMap[2]="c",
    staticMap[3]="d",
    staticMap[4]="e");

一般情况下,如果为此定义一个类,则可以在main之前执行所需的所有代码:

class MyCode
{
    MyCode()
    {
        // here be your code
    }
};
static MyCode m;

但是要小心,调试在main启动之前运行的代码通常是一个毛茸茸的混乱,最好避免。在我最近的所有项目中,几乎没有代码在全局变量的构造函数中运行,更常见的是我只是将相关的“全局变量”创建为main的本地变量,并在全局变量中存储指向它们的指针。

答案 1 :(得分:4)

这是在进入main之前执行某些代码的合法常用技术。 在C ++中,文件/命名空间范围内只允许声明,但声明可能包含函数调用(对std::map::operator[]的调用是一个)

您的示例与以下内容完全相同:

int foo()
{
   std::cout << "foo";  // You could fill your map here 
   return 42;
}

int bar = foo(); // allowed

int main() {

    return 0;
}

但更惯用的C ++方法是使用构造函数来实现相同的效果:

struct foo
{
   foo()
   {
       std::cout << "foo"; // You could fill your map here  
   }
};

foo f;

int main() {

    return 0;
}

您可以使用foo()进行地图插入。

注意:

在你的例子中,所有这些lambda都没用,可以简化为:

const char* const a = staticMap[0] = "a";
const char* const b = staticMap[1] = "b";
const char* const c = staticMap[2] = "c";
const char* const d = staticMap[3] = "d";
const char* const e = staticMap[4] = "e";

答案 2 :(得分:0)

在输入main()之前,会初始化命名空间范围内的变量。全局变量的初始化可以调用任意函数,既可以生成构造函数参数,也可以从相应的构造函数中调用。退出main()后,这些全局变量将以相反的顺序销毁。

理论上,您可以完全从这些初始化之一(或仅来自析构函数)运行实际程序。但是,这可能不是一个特别好的主意。

您发布的代码完全有效但过于复杂:无需使用lambda函数为字符串生成初始值设定项。