Chinaunix首页 | 论坛 | 博客
  • 博客访问: 8016242
  • 博文数量: 159
  • 博客积分: 10424
  • 博客等级: 少将
  • 技术积分: 14615
  • 用 户 组: 普通用户
  • 注册时间: 2010-07-14 12:45
个人简介

啦啦啦~~~

文章分类
文章存档

2015年(5)

2014年(1)

2013年(5)

2012年(10)

2011年(116)

2010年(22)

分类: C/C++

2012-01-18 23:13:49

作者:gfree.wind@gmail.com
博客:blog.focus-linux.net   linuxfocus.blog.chinaunix.net
 
 
本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。
======================================================================================================
快到春节了,心里不自然的就有些浮躁了,昨天再次break build。上一次是由于我遗漏了一个头文件,也忘了检查。但是昨天这次有些客观原因,昨天再checkin以后,特意使用另外一台机器进行了update,make all,并没有发生问题。结果今天的build没有通过,我也就知道了,这个工程的makefile文件并不完善,更新头文件时,有些依赖于该头文件的代码并没有参加编译,所以在我昨晚的检查中,问题没有发生。

下面看看我犯的错误:
当时写了一个类似于下面的枚举:
  1. #ifndef TEST_ENUM_H_
  2. #define TEST_ENUM_H_

  3. enum {
  4.     TEST_FLAG1_E,
  5.     TEST_FLAG2_E,


  6.     TEST_FLAG_NR
  7. } TEST_E;


  8. #endif
当时在enum关键字前面遗漏了“typedef”。我一般习惯于使用typedef,这样可以直接使用TEST_E而不是enum TEST_E。

该头文件会被其它源文件引用。由于在代码中,没有需要定义枚举变量的地方,只是使用枚举的值,所以当时没有发现遗漏“typedef”。编译也没有任何问题。

但是当天的build却没有通过。错误信息显示定义了重复的TEST_E,因此编译失败。由于与美国的时差问题,这个错误由美国的一同事修改了。他陈述的错误原因是:这样的枚举声明对于C来说,是没有问题的。——我们的核心代码都是C编写的。
但是对于C++,会认为不是声明而是定义,定义了一个TEST_E变量。结果导致重复定义了该变量。——有一部分web功能代码是使用C++编写的。

当我早上看到他的说明时,首先要对break build表示歉意;第二也鄙视了一下该产品的makefile——我刚刚加入这个产品组。这样的makefile,为了检查checkin,我不得不先make clean才能保证所有的代码被编译。这样花费的时间太多了。第三,我才想起web的这部分功能是使用C++的。第四,寒一下自己,居然漏写了typedef;第五,也有些好奇C++的编译为什么出错。

但是当我看到他的陈述时,我知道他肯定错了。对于C和C++来说,枚举enum的区别不会这么大。对于上面那个枚举类型定义,由于遗漏了typedef,所以这里的TEST_E无论是C还是C++来说,都会把TEST_E当作一个枚举变量处理,也就是一个全局变量。那么引起问题的原因是因为C和C++对于没有初始值的全局变量的处理不同——真拗口,而最后的编译链接行为不同。

看下面的简单示例:
文件test1.c
  1. int a;

  2. int main()
  3. {
  4.     return 0;
  5. }
文件tes2.c
  1. int a;
编译
  1. [fgao@fgao-vm-fc13 test]$ gcc -g -Wall test1.c test2.c
  2. [fgao@fgao-vm-fc13 test]$
编译没有任何的warning和error。对于没有初始值的全局变量,其为弱符号。对于多个弱符号定义,在C的链接阶段不会有任何问题。大家可以参见我这篇文章通过未初始化全局变量,研究BSS段和COMMON段的不同 http://blog.chinaunix.net/space.php?uid=23629988&do=blog&id=2888209
在这篇文章中,我解释了为什么C允许多个弱符号存在


下面将其视为C++代码,使用g++编译:
  1. [fgao@fgao-vm-fc13 test]$ g++ -g -Wall test1.c test2.c
  2. /tmp/ccQdTwRi.o:(.bss+0x0): multiple definition of `a'
  3. /tmp/cc7SOWD1.o:/home/fgao/works/test/test1.c:4: first defined here
  4. collect2: ld returned 1 exit status
同样是没有初始值的全局变量,在C++的链接阶段就会报错。对于C++为什么报错,这肯定是由于C++的链接机制有关。目前我并不清楚原因,有了解的朋友请赐教。谢谢。
阅读(9850) | 评论(9) | 转发(2) |
给主人留下些什么吧!~~

GFree_Wind2012-04-05 23:43:33

gdmmx: 我的体会是编译器对待全局变量初始化的policy是一致的,即全局变量的赋初值是由ld来完成还是由elf自身来完成。
在C语言中,其policy是由ld-linux.so来完成赋初值.....
刚才试了一下,对于builtin类型,C++仍然将其放在data区。


#include <stdlib.h>
#include <stdio.h>

int test = 1234;

int main()
{
    printf("%d\n", test);

    return 0;
}


[fgao@fgao-vm-fc13 test]$ g++

GFree_Wind2012-03-27 10:00:47

gdmmx: 我的体会是编译器对待全局变量初始化的policy是一致的,即全局变量的赋初值是由ld来完成还是由elf自身来完成。
在C语言中,其policy是由ld-linux.so来完成赋初值.....
这个也好判断一下。一会儿写个C++测试程序,看看全局变量的整数类型,是否放在data段就可以了。

gdmmx2012-03-27 00:53:53

GFree_Wind: 但是对于此例中的内置整型来说,C++是否在_init初始化,我对此表示怀疑。如果是类的话,确实是在_init中初始化。但是内置整型,应该不需要这样。

有时间可以用.....
我的体会是编译器对待全局变量初始化的policy是一致的,即全局变量的赋初值是由ld来完成还是由elf自身来完成。
在C语言中,其policy是由ld-linux.so来完成赋初值。在C++中,其policy是由elf自身来完成赋初值。这个policy不会由于变量是否为built-in类型而有所改变。
而对于后者policy,出现重复定义的link error就不难理解了。

GFree_Wind2012-03-26 21:48:51

gdmmx: 我个人的理解:
C++的全局变量在_init()的阶段会触发该变量类型的构造函数,由构造函数完成该变量的赋值(即,全局变量的初始化是由该app来完成,编译器需要在el.....
但是对于此例中的内置整型来说,C++是否在_init初始化,我对此表示怀疑。如果是类的话,确实是在_init中初始化。但是内置整型,应该不需要这样。

有时间可以用一个简单的程序试验一下。

gdmmx2012-03-26 20:35:53

我个人的理解:
C++的全局变量在_init()的阶段会触发该变量类型的构造函数,由构造函数完成该变量的赋值(即,全局变量的初始化是由该app来完成,编译器需要在elf文件中植入相应代码)。
而C的全局变量由loader(即ld)从elf的.data/.bss段直接加载到内存中(即,全局变量的初始化由ld来完成,elf文件的.text段并没有对应代码)。

或许将int a替换成class A a比较好理解,这时候断点设在A::A()函数中就能观察到A a变量在main()函数之前的初始化动作。