关于C/C++中全局变量的初始化问题的深入思考

本文通过实验验证了C和C++中全局变量的初始化时机。在C语言中,全局变量在编译期完成初始化;而在C++中,类的全局变量在程序运行期,main函数开始之前初始化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言:

前日,在一次C++课程上,刘老师在举例说明构造函数和析构函数的功能时,提到了全局变量初始化时的构造函数的行为。构造函数在main函数之前初始化全局变量。当然在C++下我是深信不疑的。但随后老师声称C语言下的全局变量也是如此,因为C没有构造和析构函数,所以我们无法看到这一过程,在C++下可以在构造和析构函数中向屏幕打印信息,进而可以观察全局变量的初始化和生存期。

这个观点无疑使我心头一震,作为C的痴迷者,长期以来在我头脑中的印象是,全局变量在编译期就完成初始化了。难道我的观念是错误的?!难道C真的也是在main函数之前,在程序运行初期才初始化?!

于是我翻看了《C语言参考手册》这本书上没有明确的答案,再翻看著名的K&R的《C程序设计语言》中只有括号里面的一句话“在概念上.......”也是含糊其辞。(现在想想这个问题可能和编译器有关,所以丹爷爷也没说明太多)

在网上查询了一下,关于这个问题,持什么观点的都有,没有一个权威的答案。

只能靠自己了,动手实验!

 

先给出我的结论

C和C++中的一般全局变量(不包括类class)是在编译期确定的初始值,而不是在程序运行时,在main函数之前初始化的。

C++中的类的全局变量是在程序运行时,在main函数之前初始化的。

 

预热知识:

C或者C++语言,明面上的入口函数是main(argc,argv),或者tmain、wmain、WinMain等等。但实际上,是C Runtime的startup代码中的void mainCRTStartup(void)函数,调用了编程者写的main函数。这个函数定义在VisualC++安装目录的crt\src\目录下的某个.c文件中(视VC++的版本不同,存放的文件也不同)。它在执行一些初始化操作,如获取命令行参数、获取环境变量值、初始化全局变量、初始化io的所需各项准备之后,调用main(argc,argv)。main函数返回后,mainCRTStartup还需要调用全局变量的析构函数或者atexit()所登记的一些函数。往深里说,是在链接生成可执行文件时,告诉链接器这个可执行文件的entry就是mainCRTStartup。当然,我们也可以对编译器进行设置,使其不插入mainCRTStartup函数代码

以VC++6.0为例设置:Project->Settings->Link 在Category中选择Output,在Entry-point symbol中填上main 即可。

-------------------------

实验一:

1,     C语言环境下:

实验准备:

int a ;
int main(void)
{
       return a+3 ;
}

在编译器中设置入口函数为main(具体方法见上面)

这样,我们让编译器生成的程序,直接从main函数中进入,而不是先执行mainCRTStartup函数做一些准备工作。

结果预测:

这样,如果函数返回的是3,则说明此全局变量是在编译期就被初始化为0了,如果函数返回的是其它数字,则说明此全局变量是在程序运行时,main函数运行前进行的初始化。

实验结果:

进入控制台(运行cmd命令),运行编译后的程序(因为程序没有向屏幕输出结果,我们看不到任何现象),继续输入命令:echo  %ERRORLEVEL% 则显示3,此即为函数的返回值。

(echo是显示其后的值,系统把前面运行的程序的返回值放在%ERRORLEVEL%中,故我们可以通过此方法获得主函数的返回值)

 

同理:对于结构体全局变量

struct A
{
       int a ;     
} sTest;
int main(void)
{
       return sTest.a+3 ;
}

函数也返回3.


实验结论:

在C语言中,全局变量是在编译期完成初始化的。

(在本实验中我们没有使用I/O函数把结果打印出来,因为I/O函数的调用之前必须要初始化内存中的某堆空间,而这个工作是由main函数之前的mainCRTStartup函数来做的。而我们设置让编译器跳过这个函数,故会在运行时出错。)

 

实验二:

C++语言环境下

实验准备:

class A
{
public:
       int a ;     
       A(){a=10;}
       ~A(){}
} ;
A cTest ;
 
int main(void)
{
       return cTest.a ;
}

结果预测:

这样,如果函数返回的是0,则说明此全局变量是在编译期就被初始化为0了,如果函数返回的是其它数字,则说明此全局变量是在程序运行时,main函数运行前进行的初始化。

 

实验结果:

在编译器中设置入口函数为main,主函数返回一个其他值

在编译器中设置入口函数为默认,主函数返回值为10

实验结论:

在C++中,类(class)的全局变量是在程序运行期,main函数开始之前,调用类的构造函数完成初始化的。

同理:

把C中的代码放到C++下实验

int a ;
int main(void)
{
       return a+3 ;
}

结果与C的结果相同。

说明:在C++中一般全局变量的初始化(类除外),是在编译期完成的,而不是在运行期完成。(与C语言规则相同)

mainCRTStartup函数不管一般全局变量的初始化,它管理类(class)的全局变量的初始化,调用类的析构函数。

编译器会在编译时,初始化一般全局变量为0.

 

 

另:具有全局生命期的局部静态变量的初始化,与局部变量相同都是在运行时,执行到该初始化语句完成初始化的,只是局部静态变量只初始化一次。

 

后记:

1、程序不是从主函数开始执行的,而是先要执行一些启动代码。(现在明白为什么要在在嵌入式软件编程时要在工程中添加类似于75x_init.s和75x_vect.s这两个汇编文件了吧)

2、你应该给主函数以返回值。实际上标准C只规定了两种形式的main函数:

int main( void ) 和 int main(int argc, char *argv[])

main返回0,告诉系统程序正常终止,返回非零值告诉系统程序异常关闭.

其作用:我们可以利用程序的返回值,控制要不要执行下一个程序。

例:程序名&&DOS命令

前面的程序正常执行后才执行后面的DOS命令。当然我们也可以用其它的逻辑符把程序和命令组织起来,来实现复杂的功能。

(UNIX中的shell命令也有类似功能)

 

 

 

在C语言中,变量的初始化和赋值是两个相关但不同的概念。初始化是指在变量声明时为其赋予一个初始值,而赋值则是在变量声明之后通过赋值操作符(如`=`)为其赋予新的值。以下是关于变量初始化与赋值的详细说明及示例。 ### 变量的初始化 初始化通常发生在变量定义的同时,确保变量从一开始就具有一个明确的值。这种方式可以避免未初始化变量带来的不确定行为。 - **显式初始化**:在声明变量时直接为其赋值。 ```c int a = 10; // 整型变量的显式初始化 float b = 3.14f; // 浮点型变量的显式初始化 char c = 'A'; // 字符型变量的显式初始化 ``` - **隐式初始化**:某些情况下,编译器会为全局变量或静态变量自动进行默认初始化(例如整型变量默认为0)。 ```c static int d; // 静态变量的隐式初始化,默认值为0 ``` ### 变量的赋值 赋值是指在变量已经声明的前提下,使用赋值操作符`=`将其绑定到一个新的值上。这种方式可以在程序运行过程中动态地修改变量的值。 - **基本数据类型的赋值**: ```c int e; e = 20; // 声明后进行赋值 float f = 5.67f; // 初始化 f = 8.9f; // 再次赋值 ``` - **结构体的初始化与赋值**: 结构体是一种复合数据类型,其成员可以是不同的数据类型。结构体变量的初始化可以在定义时完成,也可以在后续进行赋值。 ```c struct Point { int x; int y; }; struct Point p1 = {1, 2}; // 定义并初始化结构体变量 struct Point p2; p2.x = 3; // 赋值x成员 p2.y = 4; // 赋值y成员 ``` - **数组的初始化与赋值**: 数组的初始化可以通过指定初始值列表来完成,而赋值则需要逐个元素进行。 ```c int arr[5] = {1, 2, 3, 4, 5}; // 初始化数组 int arr2[5]; arr2[0] = 10; // 赋值第一个元素 arr2[1] = 20; // 赋值第二个元素 ``` ### 示例代码 以下是一个完整的示例,展示了变量的初始化与赋值过程: ```c #include <stdio.h> struct Point { int x; int y; }; int main() { // 基本数据类型的初始化与赋值 int a = 10; // 初始化 int b; b = 20; // 赋值 float c = 3.14f; // 初始化 c = 2.71f; // 再次赋值 // 结构体的初始化与赋值 struct Point p1 = {1, 2}; // 初始化结构体 struct Point p2; p2.x = 3; // 赋值x成员 p2.y = 4; // 赋值y成员 // 输出结果 printf("a = %d\n", a); printf("b = %d\n", b); printf("c = %.2f\n", c); printf("p1: x = %d, y = %d\n", p1.x, p1.y); printf("p2: x = %d, y = %d\n", p2.x, p2.y); return 0; } ``` ### 总结 - 初始化是在变量声明时赋予初始值的过程,有助于避免未定义行为。 - 赋值是在变量声明后通过操作符`=`改变其值的过程。 - 对于复杂的数据结构(如结构体、数组),初始化和赋值的方式略有不同,需根据具体情况进行处理。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值