1. 程式人生 > >從gcc區域性static變數初始化看C/C++區別

從gcc區域性static變數初始化看C/C++區別

http://tsecer.blog.163.com/blog/static/15018172012259354952/

====================================================

一、區域性/全域性變數
區域性變數在C++中的使用要頻繁的多,並且功能也強大的多,但是這些強大功能的背後無疑會引入問題的複雜性,不想讓馬兒吃草只想讓馬兒跑的事大家表亂想。這些初始化的實現就需要C++的庫執行更多的動作來完成,雖然各種編譯器都是像如今開展的“學雷鋒”活動一樣幹了很多好事都沒有留名,但是作為一個程式設計師,還是要對別人的貢獻進行表彰。
我們看一下下面的一段程式碼,本文將會圍繞這個程式碼進行展開,可以看到這個簡單的程式,讓C++生成了非常多的程式碼讓人應接不暇


[[email protected] localstatic]$ cat localstatic.c             
extern int foo();
int globvar = foo();
int bar()
{
static int localvar = foo();
return localvar;
}
[[email protected] localstatic]$ gcc localstatic.c  -c
localstatic.c:2: error: initializer element is not constant
localstatic.c: In function ‘bar’:

localstatic.c:5: error: initializer element is not constant
[[email protected] localstatic]$ g++ localstatic.c  -c
[[email protected] localstatic]$ objdump -rdCh localstatic.o 

localstatic.o:     file format elf32-i386

Sections:
Idx Name          Size      VMA       LMA       File off  Algn

  0 .text         000000b1  00000000  00000000  00000034  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000000  00000000  00000000  000000e8  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000014  00000000  00000000  000000e8  2**3
                  ALLOC
  3 .gcc_except_table 0000000c  00000000  00000000  000000e8  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .ctors        00000004  00000000  00000000  000000f4  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, DATA
  5 .comment      0000002d  00000000  00000000  000000f8  2**0
                  CONTENTS, READONLY
  6 .note.GNU-stack 00000000  00000000  00000000  00000125  2**0
                  CONTENTS, READONLY
  7 .eh_frame     000000ac  00000000  00000000  00000128  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA

Disassembly of section .text:

00000000 <bar()>:
   0:    55                       push   %ebp
   1:    89 e5                    mov    %esp,%ebp
   3:    57                       push   %edi
   4:    56                       push   %esi
   5:    53                       push   %ebx
   6:    83 ec 1c                 sub    $0x1c,%esp
   9:    b8 08 00 00 00           mov    $0x8,%eax
            a: R_386_32    .bss
   e:    0f b6 00                 movzbl (%eax),%eax
  11:    84 c0                    test   %al,%al
  13:    75 52                    jne    67 <bar()+0x67>
  15:    c7 04 24 08 00 00 00     movl   $0x8,(%esp)
            18: R_386_32    .bss
  1c:    e8 fc ff ff ff           call   1d <bar()+0x1d>
            1d: R_386_PC32    __cxa_guard_acquire當獲得鎖之後再次判斷,這裡也是避免多執行緒競爭的關鍵一步,此時可以保證之後操作原子性
  21:    85 c0                    test   %eax,%eax
  23:    0f 95 c0                 setne  %al
  26:    84 c0                    test   %al,%al
  28:    74 3d                    je     67 <bar()+0x67>
  2a:    bb 00 00 00 00           mov    $0x0,%ebx
  2f:    e8 fc ff ff ff           call   30 <bar()+0x30>
            30: R_386_PC32    foo()
  34:    a3 10 00 00 00           mov    %eax,0x10
            35: R_386_32    .bss
  39:    c7 04 24 08 00 00 00     movl   $0x8,(%esp)
            3c: R_386_32    .bss
  40:    e8 fc ff ff ff           call   41 <bar()+0x41>
            41: R_386_PC32    __cxa_guard_release
  45:    eb 20                    jmp    67 <bar()+0x67>
  47:    89 d6                    mov    %edx,%esi
  49:    89 c7                    mov    %eax,%edi
  4b:    84 db                    test   %bl,%bl
  4d:    75 0c                    jne    5b <bar()+0x5b>
  4f:    c7 04 24 08 00 00 00     movl   $0x8,(%esp)
            52: R_386_32    .bss
  56:    e8 fc ff ff ff           call   57 <bar()+0x57>
            57: R_386_PC32    __cxa_guard_abort
  5b:    89 f8                    mov    %edi,%eax
  5d:    89 f2                    mov    %esi,%edx
  5f:    89 04 24                 mov    %eax,(%esp)
  62:    e8 fc ff ff ff           call   63 <bar()+0x63>
            63: R_386_PC32    _Unwind_Resume
  67:    a1 10 00 00 00           mov    0x10,%eax
            68: R_386_32    .bss
  6c:    83 c4 1c                 add    $0x1c,%esp
  6f:    5b                       pop    %ebx
  70:    5e                       pop    %esi
  71:    5f                       pop    %edi
  72:    5d                       pop    %ebp
  73:    c3                       ret    

00000074 <__static_initialization_and_destruction_0(int, int)>:
  74:    55                       push   %ebp
  75:    89 e5                    mov    %esp,%ebp
  77:    83 ec 08                 sub    $0x8,%esp
  7a:    83 7d 08 01              cmpl   $0x1,0x8(%ebp)
  7e:    75 13                    jne    93 <__static_initialization_and_destruction_0(int, int)+0x1f>
  80:    81 7d 0c ff ff 00 00     cmpl   $0xffff,0xc(%ebp)
  87:    75 0a                    jne    93 <__static_initialization_and_destruction_0(int, int)+0x1f>
  89:    e8 fc ff ff ff           call   8a <__static_initialization_and_destruction_0(int, int)+0x16>
            8a: R_386_PC32    foo()
  8e:    a3 00 00 00 00           mov    %eax,0x0
            8f: R_386_32    globvar
  93:    c9                       leave  
  94:    c3                       ret    

00000095 <global constructors keyed to globvar>:
  95:    55                       push   %ebp
  96:    89 e5                    mov    %esp,%ebp
  98:    83 ec 18                 sub    $0x18,%esp
  9b:    c7 44 24 04 ff ff 00     movl   $0xffff,0x4(%esp)
  a2:    00 
  a3:    c7 04 24 01 00 00 00     movl   $0x1,(%esp)
  aa:    e8 c5 ff ff ff           call   74 <__static_initialization_and_destruction_0(int, int)>
  af:    c9                       leave  
  b0:    c3                       ret    
[[email protected] localstatic]
這裡可以看出幾點比較有趣的內容:
1、非常量變數對於全域性變數和靜態區域性變數的初始化使用gcc無法編譯通過,但是使用g++可以編譯通過。而兩者的區別在於gcc會把這個.c字尾的程式看做一個C程式,而g++則把這個.c字尾的看做c++檔案,而c++語法是允許對變數進行更為複雜的初始化。
2、全域性變數的初始化實現使用了.ctors節,該節中儲存了該編譯單元中所有需要在main函式之前呼叫的初始化函式,其中對於globvar的賦值就在該函式中完成。
3、區域性靜態變數的初始化,它要保證任意多個函式被呼叫,它只初始化一次,並且只能被初始化一次,並且這個初始化只能在執行到的時候執行,假設說這個bar函式從來沒有在執行時執行過,那麼這個區域性變數的賦值就用完不能被執行到。
4、裡面有一些比較複雜的__cxa_guard_acquire操作,它在哪裡定義,用來做什麼?
二、全域性變數的初始化
1、初始化程式碼位置確定
這個正如之前說過的,它需要在main函式執行之前執行,
[[email protected] localstatic]$ objdump -r localstatic.o
……
RELOCATION RECORDS FOR [.ctors]:
OFFSET   TYPE              VALUE 
00000000 R_386_32          .text
然後通過hexdump看一下這個地方的內容
[[email protected] localstatic]$ hexdump localstatic.o 
0000000 457f 464c 0101 0001 0000 0000 0000 0000
0000010 0001 0003 0001 0000 0000 0000 0000 0000
0000020 0248 0000 0000 0000 0034 0000 0000 0028
0000030 000f 000c 8955 57e5 5356 ec83 b81c 0008
0000040 0000 b60f 8400 75c0 c752 2404 0008 0000
0000050 fce8 ffff 85ff 0fc0 c095 c084 3d74 00bb
0000060 0000 e800 fffc ffff 10a3 0000 c700 2404
0000070 0008 0000 fce8 ffff ebff 8920 89d6 84c7
0000080 75db c70c 2404 0008 0000 fce8 ffff 89ff
0000090 89f8 89f2 2404 fce8 ffff a1ff 0010 0000
00000a0 c483 5b1c 5f5e c35d 8955 83e5 08ec 7d83
00000b0 0108 1375 7d81 ff0c 00ff 7500 e80a fffc
00000c0 ffff 00a3 0000 c900 55c3 e589 ec83 c718
00000d0 2444 ff04 00ff c700 2404 0001 0000 c5e8

相關推薦

gcc區域性static變數初始C/C++區別

http://tsecer.blog.163.com/blog/static/15018172012259354952/ ==================================================== 一、區域性/全域性變數 區域性變數

static變數初始到Spring/Spring boot的工具類靜態變數注入

寫這篇博文,要從java.lang.ExceptionInInitializerError這個報錯開始。簡單的看上去,這是一個類初始化異常報錯。但事實上並不是這樣,這是由於呼叫某個static變數屬

C++函式中的static變數初始及析構順序

有如下的類: class base { public: base(char* pStr) { m_pStr = pStr; cout << pStr << " Constructor!" << endl; } ~base(

c語言裡面變數初始問題與Java區別

C語言中,定義區域性變數時如果未初始化,則值是隨機的,為什麼? 定義區域性變數,其實就是在棧中通過移動棧指標來給程式提供一個記憶體空間和這個區域性變數名繫結。因為這段記憶體空間在棧上,而棧記憶體是反覆使用的(髒的,上次用完沒清零的),所以說使用棧來實現的區域性變數定義時如果不顯式初始化,值

C/C++關於全域性變數區域性變數初始與不初始區別

在C語言裡,全域性變數如果不初始化的話,預設為0,也就是說在全域性空間裡:int x =0; 跟 int x; 的效果看起來是一樣的。但其實這裡面的差別很大,強烈建議大家所有的全域性變數都要初始化,他們的主要差別如下: 編譯器在編譯的時候針對這兩種情況會產生兩種符號

static程式碼塊、成員變數初始、構造方法執行順序

下面程式碼: public class Son{ Father father = new Father(); static{ System.out.println("Son static"); } publi

IAR 區域性變數初始的問題

今天除錯UCOSiii的程式,遇到一個問題,程式停止在等待PLL工作的while迴圈中。硬體環境: stm32f439igt單板軟體環境: UCOSIII,程式碼從USOS官網直接下單,沒有做任何改動編譯環境: IAR最近有個專案用到了STM32F439, 客戶要求跑一個小的

全域性變數區域性變數初始及預設值探究

public class VariableTest { class Person { private String familyName; private String lastName = "33"; private int age; private I

final 類變數 區域性變數 初始

區域性變數沒有預設值:所以在使用前一定要初始化,要麼宣告時初始化,要麼先聲明後賦值(不管是否是final的): int a=9; int b; b=0; 類的成員變數: 非final的變數 有預設的初始值,所以可以不顯示的賦值 但final的要初始化

final和static final 變數初始

對於final型別的變數,對於不加static我們可以有兩種方式給它賦值:宣告變數時直接賦值;在構造方法中完成賦值,如果一個類有多個構造方法,就要保證在每個構造方法中都要完成對該final型別變數的初始化工作。對於一個變

ARM區域性變數初始

1、函式內部的區域性變數如何初始化,如以下程式中的cost_a、const_b、const_c: extern int main( void ) { uint32_t const_a = 0x12345678; uint32_t const_b = 0x8765432

全域性變數區域性變數初始問題

標頭檔案: #ifndef FRIEND_H #define FRIEND_H struct X; struct Y{ void f(X*); }; struct X{ private : int i; public : void initialize(); fr

java變數初始順序

在類的內部,變數定義的先後順序決定了初始化順序,即使變數定義分散在方法定義之外,它們依舊會在任何方法(包括構造器)被呼叫之前得到初始化 其中初始化的順序先是靜態物件,其後是非靜態物件 例如: class Cat{ private Dog dog1=new Dog(1)

關於C++的變數初始

建構函式的初始化,不要寫成函式的形式,比如const型別的資料是無法通過:construct(int a) { p1= a; p2 =a; //const 不能直接賦值初始化 p3 =p1; //p3沒有被初始化 } int p1; const int p2; int

C程式碼開發遇到的問題 變數初始和結構體指標移動

1. 變數初始化 函式內部的變數如果不初始化的話預設不是0而是一個隨機值。 下面的程式用來列印一個未初始化的無符號的整型值,執行幾遍,每次的結果都會不一樣 #include <stdio.h> void PrintUint() { /* 預設是隨機值,不一定是0 *

易學筆記-go語言-第4章:基本結構和基本資料型別/4.4 變數/4.4.3 函式體內最簡單的變數初始

函式體內最簡單的變數賦值 格式:  變數名 := 值 舉例: var goos string = os.Getenv("GOOS") fmt.Printf("The operating system is: %s\n", goos) //函式體內最

Java靜態變數初始及建構函式的執行順序與執行時機分析

    對於Java初學者來說,關於靜態變數、非靜態變數的初始化時機與順序,以及建構函式的執行時機與順序都會感覺有點理不清頭緒,下面文章使用例項程式幫大家解決這方面的疑惑。雖然簡單,但是對Java入門者來說還是有一定的幫助作用。    

指標變數初始的重要性

自己聲明瞭一個指標,但是沒有初始化,成為了野指標,當程式開啟後,不做任何操作,所以沒有給該指標申請記憶體空間,當我直接關閉程式時,程式出現了異常中斷,如下: 因為指標變數被預設為野指標,所以就進入了釋放記憶體指標所指的記憶體空間的操作; 當我將該指標初始化後,程式沒有發生異常中斷情況,好

c++ 類成員變數初始順序

#include <iostream> using namespace std; class A { public: //使用初始化列表初始化時,與定義成員變數的順序有關。 //因為成員變數的初始化次序是根據變數在

tf.get_variable 中變數初始函式和Xavier初始

當使用 tf.get_variable(name, shape=None, initializer=None) 來定義變數時,可以利用變數初始化函式來實現對 initializer 的賦值。 在神經網路中,最常權重賦值方式是 正態隨機賦值 和 Xavier賦值。 1. 變數初始