ISO/IEC 14882:2011之条款3.6——开始与结束
3.6 开始与结束
3.6.1 主函数
1、一个程序应该包含一个称作为main的全局函数,它是程序指定的起始函数。在独立环境中的一个程序是否需要定义一个main函数是由实现定义的。[注:在一个独立的环境中,启动和终止是实现定义的;启动包含了带有静态存储周期的名字空间作用域的对象的构造器的执行;终止包含了带有静态存储周期的对象的析构器的执行。 —— 注结束]
2、一个实现不应该预定义main函数。这个函数不应该被重载。它应该有类型int的一个返回类型,但或者其类型是实现定义的。所有实现应该允许main的以下两种定义:
int main() { /* ... */ }
以及
int main(int argc, char* argv[]) { /* ... */ }
在后一种形式中,argc应该是从程序正在运行的环境传递到程序的实参个数。如果argc是非零,那么这些实参应该在argv[0]到argv[argc - 1]中提供,每个元素指向null结尾的多字节字符串(NTMBS)的首字符(17.5.2.1.4.2),而argv[0]应该是指向表示用于调用程序的名字或""的一个NTMBS的首字符。argc的值应该是非负的。argv[argc]的值应该是0。[注:建议任一后续(可选的)形参在argv后添加。 ——注结束]
3、函数main不应该在一个程序内使用。main的连接(3.5)是由实现定义的。将main定义为被删除的,或将main声明为inline、static或constexpr的一个程序是不良形式的。如果这样的话,main的名字不会被保留。[例:成员函数,类,以及枚举可以被称为main,在其它名字空间中的实体也能如此。 ——例结束]
4、不离开当前的语句块而终止程序(比如,通过调用std::exit(int)(18.5))并不销毁任何具有自动存储周期的对象(12.4)。如果std::exit在销毁具有静态或线程存储周期的一个对象期间被调用以结束一个程序,那么该程序具有未定义的行为。
5、main中的一条返回语句具有离开main函数的效果(销毁所有带有自动存储周期的对象)并用返回值作为实参调用std::exit。如果控制到达main的末尾而没有遇到一条return语句,那么效果就是return 0;
3.6.2 非局部变量的初始化
1、有两种广义类型的命名的非局部的变量:那些带有静态存储周期(3.7.1)以及带有线程存储周期(3.7.2)的。带有静态存储周期的非局部变量被初始化为程序初始化的一个结果。带有线程存储周期的非局部变量被初始化为线程执行的一个结果。在初始化的这些阶段的每一个内,初始化如下发生。
2、带有静态存储周期(3.7.1)的变量或线程存储周期(3.7.2)的变量应该在任何其它初始化发生前被用零初始化的(8.5)。
常量初始化被执行:
—— 如果出现在带有静态或线程存储周期的一个引用的初始化中的每个完整表达式(包括隐式转换)是一个常量表达式(5.19)并且该引用被绑定到指派具有静态存储周期的一个对象的一个左值或绑定到一个临时变量(见12.2);
—— 如果具有静态或线程存储周期的一个对象由一个构造器调用初始化,如果该构造器是一个constexpr构造器,如果所有的构造器实参是常量表达式(包括转换),并且如果在函数调用替换(7.1.5)之后,在为非静态数据成员的mem-initializers中以及在brace-or-equal-initializers中的每个构造器调用和完整表达式是一个常量表达式;
—— 如果带有静态或线程存储周期的一个对象不被一个构造器初始化,并且如果出现在其初始化器中的每个完整的表达式是一个常量表达式。
零初始化和常量初始化统称为静态初始化;所有其它初始化都是动态初始化。静态初始化应该在任一动态初始化发生之前被执行。具有静态存储周期的一个非局部变量的动态初始化或按次序或不按次序执行。显式特化的类模板静态数据成员的定义具有按次序的初始化。其它类模板静态数据成员(即,隐式或显式地实例化的特化)具有不按次序的初始化。其它具有静态存储周期的非局部变量具有按次序的初始化。带有定义在一单个翻译单元内的按次序的初始化的变量应该以它们在翻译单元中的如果一个程序启动了一个线程(30.3),那么一个变量的后续初始化对于定义在不同翻译单元中的一个变量的初始化不被按先后顺序串接。否则的话,一个变量的初始化对于定义在一个不同翻译单元中的一个变量的初始化以不确定的顺序进行。如果一个程序启动了一个线程,那么后面一个变量不按次序的初始化对于每个其它动态初始化是不确定次序的。[注:这个定义允许一串按次序的变量的初始化并发地与另一串进行。 ——注结束][注:局部静态变量的初始化在6.7中描述。 ——注结束]
3、允许一个实现执行一个具有静态存储周期的非局部变量的初始化作为一次静态初始化,即使这样的初始化不要求被静态地完成,提供了
—— 初始化的动态版本在其初始化之前并不改变名字空间作用域的任何其它对象的值,并且
—— 初始化的静态版本在被初始化的变量中产生了与由动态初始化所产生的相同的值,如果所有不需要被静态初始化的变量被动态初始化。
[注:作为结果,如果一个obj1对象的初始化引用了潜在需要动态初始化并稍后在同一翻译单元中定义的名字空间作用域的一个obj2对象,那么所使用的obj2的值是被完全初始化的obj2的值(因为obj2被静态初始化)还是仅仅被零初始化的obj2的值是未指定的。比如,
inline double fd() { return 1.0; } extern double d1; double d2 = d1; // 未被指定的: // 可以被初始化为0.0,如果d1被动态初始化;否则的话,可以是1.0 double d1 = fd(); // 可以被静态初始化或动态初始化为1.0
—— 注结束]
4、具有静态存储周期的一个非局部变量的动态初始化是否在main函数的第一条语句执行前完成是由实现定义的。如果初始化被延迟到main函数的第一条语句后的某一个点,那么它将在定义在同一个翻译单元的中的任一函数或变量的第一个odr-used【译者注:使用一次定义规则的】(3.2)之前发生,当变量被初始化时。【注:具有含带副作用的初始化的静态存储周期的一个非局部变量必须被初始化,即使它并不使用odr-used(3.2,3.7.1)】【例:
// 文件1 #include "a.h" #include "b.h" B b; A::A(){ b.Use(); } // 文件2 #include "a.h" A a; // 文件3 #include "a.h" #include "b.h" extern A a; extern B b; int main() { a.Use(); b.Use(); }
a或b是否在进入main函数之前被初始化,或初始化是否被延迟到main函数中的第一个odr-used,是由实现定义的。特别地,如果在进入main函数之前a被初始化,则不能保证b在a的初始化使用一次定义规则之前(即A::A被调用之前)将被初始化。然而,如果a在main函数的第一条语句之后的某个点上被初始化,那么b将在A::A中使用b之前被初始化。 —— 例结束】
5、具有静态或线程存储周期的一个非局部变量的动态初始化是否在该线程的初始函数的第一条语句之前完成是由实现定义的。如果初始化被延迟到该线程的初始函数的第一条语句之后的某个点上,那么它应该应该在带有线程存储周期的、定义在同一翻译单元中的任一变量第一次使用一次定义规则(3.2)之前发生,当该变量在被初始化时。
6、如果带有静态或线程存储周期的一个非局部变量的初始化通过一个异常而存在,那么std::terminate被调用(15.5.1)。
3.6.3 结束
1、具有静态存储周期的对已初始化对象的析构器(12.4)(即,对象的生命周期(3.8)已经开始)被调用,作为从main返回的一个结果以及调用std::exit(18.5)的一个结果。对在一个给定的线程内的具有线程存储周期的已初始化对象的析构器,作为从那个线程的初始函数所返回的一个结果以及作为那个线程调用std::exit的一个结果。在那个线程内的具有线程存储周期的所有已初始化对象的析构器的完成,在具有静态存储周期的任一对象的析构器的开始之前被串行处理。如果具有线程存储周期的一个对象的构造器或动态初始化的完成,在另一个对象的之前被串行处理,那么第二个析构器的完成在第一个析构器的开始之前被串行处理。如果带有静态存储周期的一个对象的构造器或动态初始化的完成在另一个的构造器或动态初始化的完成之前被串行处理,第二个的析构器的完成在第一个的析构器的开始之前被串行执行。【注:这个定义允许并发析构。 ——注结束】如果一个对象被静态初始化,那么该对象以相同的次序被销毁,就好似该对象被动态初始化一样。对于一个数组或类类型的对象,那个对象的所有子对象在任一带有静态存储周期的语句块作用域的对象——它在子对象的构造期间被初始化——销毁之前被销毁。如果带有静态或线程存储周期的一个对象的析构通过一个异常退出,那么std::terminate被调用(15.5.1)。
2、如果一个函数包含一个静态或线程周期的已经被销毁的语句块对象,并且该函数在具有静态或线程存储周期的一个对象的析构期间被调用,那么程序具有未定义行为,如果控制流经过先前被销毁的语句块作用域的对象的定义。同样,如果语句块作用域对象在其销毁后被间接使用(即,通过一个指针),那么行为也是未定义的。
3、如果具有静态存储周期的一个对象的初始化的完成在调用std::atexit(见<cstdlib>,18.5)之前被串行执行,那么对传递到std::atexit的函数的调用在对该对象的析构器的调用之前被串行执行。如果对std::atexit的一个调用在具有静态存储周期的一个对象的初始化的完成之前被串行执行,那么对该对象的析构器的调用在对传递到std::atexit的函数调用之前被串行执行。如果对std::atexit的一次调用在另一个对std::atexit的调用之前被串行执行,那么对传递到第二个std::atexit调用的函数的调用在对传递到第一个std::atexit调用的函数的调用之前被串行执行。
4、如果有在信号处理例程内(18.10)不被允许的一个标准库对象或函数的使用,而这个在具有静态周期的对象的销毁以及执行std::atexit注册函数(18.5)的完成之前不发生,那么程序具有未定义行为。【注:如果有对一个具有静态存储周期的对象的使用而在该对象的销毁前并不发生,那么程序具有未定义行为。在对std::exit的调用前终止每一个线程或从main退出是足够的,但不是必须的,来满足这些要求。这些要求允许线程管理器作为静态存储周期对象。 ——注结束】
5、调用声明在<cstdlib>的函数std::abort(),终止程序而不执行任一析构器也不执行传递到std::atexit()或std::at_quick_exit()的函数。