2012年9月23日 星期日

c, c++ 有著不同的 global variable initialize

書到用時方恨少, 事非經過不知難。
the 1st edtition: 20120923
the 2st edtition: 20160114

cppb.c
1 int addr;
2 char *vb = (char*)(0xb8000+160 - addr);
3 
4 int cpp_main(void)
5 {
6   return 0;
7 }

這是 c 程式, 還是 c++ 程式呢?還是兩者都是, 我認為是 c++ 程式, 因為 c compiler 無法 compile 它。

不知道這個術語要怎麼說, L2 的初始化用 c 編譯器是不會過的, 會有這個訊息 initializer element is not constant

descent@linux:dos_cpp$ gcc-4.7  
-std=c99 -fno-stack-protector -m32 -ffreestanding 
-fno-builtin -g   -c -o cppb.o cppb.c
cppb.c:2:1: error: initializer element is not constant

descent@linux:dos_cpp$ g++-4.7  -fno-stack-protector -m32 
-ffreestanding -fno-builtin -g   -c -o cppb.o cppb.c

clang 的訊息似乎比較清楚:

linux:test# clang -c cppb.c 
cppb.c:3:13: error: initializer element is not a compile-time constant
 char *vb = (char*)(0xb8000+160 - addr);
            ^~~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.

linux:test# clang++ -c cppb.c 
clang: warning: treating 'c' input as 'c++' when in C++ mode, 
  this behavior is deprecated

威力強大的 c++ 自然沒有問題。

但是 ...但是 ... 這是要付出代價的 (邪惡的 c++ compiler 又幫我們把程式碼變大了) 而且還算是蠻嚴重的代價 (相對於自己寫的程式碼大小), 要完成這個 statement 並沒有想像中的簡單:

c.dis
 1 
 2 cppb.o:     file format elf32-i386
 3 
 4 
 5 Disassembly of section .text:
 6 
 7 00000000 <_Z8cpp_mainv>:
 8    0: 55                    push   %ebp
 9    1: 89 e5                 mov    %esp,%ebp
10    3: b8 00 00 00 00        mov    $0x0,%eax
11    8: 5d                    pop    %ebp
12    9: c3                    ret    
13 
14 0000000a <_Z41__static_initialization_and_destruction_0ii>:
15    a: 55                    push   %ebp
16    b: 89 e5                 mov    %esp,%ebp
17    d: 83 7d 08 01           cmpl   $0x1,0x8(%ebp)
18   11: 75 1e                 jne    31 <_Z41__static_initialization_and_destruction_0ii+0x27>
19   13: 81 7d 0c ff ff 00 00  cmpl   $0xffff,0xc(%ebp)
20   1a: 75 15                 jne    31 <_Z41__static_initialization_and_destruction_0ii+0x27>
21   1c: a1 00 00 00 00        mov    0x0,%eax
22   21: ba a0 80 0b 00        mov    $0xb80a0,%edx
23   26: 89 d1                 mov    %edx,%ecx
24   28: 29 c1                 sub    %eax,%ecx
25   2a: 89 c8                 mov    %ecx,%eax
26   2c: a3 00 00 00 00        mov    %eax,0x0
27   31: 5d                    pop    %ebp
28   32: c3                    ret    
29 
30 00000033 <_GLOBAL__sub_I_addr>:
31   33: 55                    push   %ebp
32   34: 89 e5                 mov    %esp,%ebp
33   36: 83 ec 08              sub    $0x8,%esp
34   39: c7 44 24 04 ff ff 00  movl   $0xffff,0x4(%esp)
35   40: 00 
36   41: c7 04 24 01 00 00 00  movl   $0x1,(%esp)
37   48: e8 bd ff ff ff        call   a <_Z41__static_initialization_and_destruction_0ii>
38   4d: c9                    leave  
39   4e: c3                    ret    

L14, L30 有著令我們熟悉的 symbol, 看看之前的 global object ctor 文章, 沒錯吧, 在那系列文章我提到了這是由 compiler 幫我們生成產生的函式。

我們明明沒有宣告 global object, 為什麼也會產生這樣的函數呢?為了做 c 做不到的事情, vb 這個變數被當成 global object 來對待了。_Z41__static_initialization_and_destruction_0ii 會執行
 
2 char *vb = (char*)(0xb8000+160 - addr);

所以 vb 的值才會正確, 注意到這段反組譯程式碼, 因為沒有 link, 所以有些位址是錯的, 不過我的重點不在這邊, 而是 vb 是和 global object 一樣的處理方式。

看看產生的組合語言檔, 很長, 但只要看 L103 就好:

g++-4.7  -fno-stack-protector -m32 -ffreestanding -fno-builtin -g   -S cppb.c
  1  .file "cppb.c"
  2  .text
  3 .Ltext0:
  4  .globl addr
  5  .bss
  6  .align 4
  7  .type addr, @object
  8  .size addr, 4
  9 addr:
 10  .zero 4
 11  .globl vb
 12  .align 4
 13  .type vb, @object
 14  .size vb, 4
 15 vb:
 16  .zero 4
 17  .text
 18  .globl _Z8cpp_mainv
 19  .type _Z8cpp_mainv, @function
 20 _Z8cpp_mainv:
 21 .LFB0:
 22  .file 1 "cppb.c"
 23  .loc 1 5 0
 24  .cfi_startproc
 25  pushl %ebp
 26 .LCFI0:
 27  .cfi_def_cfa_offset 8
 28  .cfi_offset 5, -8
 29  movl %esp, %ebp
 30 .LCFI1:
 31  .cfi_def_cfa_register 5
 32  .loc 1 6 0
 33  movl $0, %eax
 34  .loc 1 7 0
 35  popl %ebp
 36 .LCFI2:
 37  .cfi_restore 5
 38  .cfi_def_cfa 4, 4
 39  ret
 40  .cfi_endproc
 41 .LFE0:
 42  .size _Z8cpp_mainv, .-_Z8cpp_mainv
 43  .type _Z41__static_initialization_and_destruction_0ii, @function
 44 _Z41__static_initialization_and_destruction_0ii:
 45 .LFB1:
 46  .loc 1 7 0
 47  .cfi_startproc
 48  pushl %ebp
 49 .LCFI3:
 50  .cfi_def_cfa_offset 8
 51  .cfi_offset 5, -8
 52  movl %esp, %ebp
 53 .LCFI4:
 54  .cfi_def_cfa_register 5
 55  .loc 1 7 0
 56  cmpl $1, 8(%ebp)
 57  jne .L3
 58  .loc 1 7 0 is_stmt 0 discriminator 1
 59  cmpl $65535, 12(%ebp)
 60  jne .L3
 61  .loc 1 2 0 is_stmt 1
 62  movl addr, %eax
 63  movl $753824, %edx
 64  movl %edx, %ecx
 65  subl %eax, %ecx
 66  movl %ecx, %eax
 67  movl %eax, vb
 68 .L3:
 69  .loc 1 7 0
 70  popl %ebp
 71 .LCFI5:
 72  .cfi_restore 5
 73  .cfi_def_cfa 4, 4
 74  ret
 75  .cfi_endproc
 76 .LFE1:
 77  .size _Z41__static_initialization_and_destruction_0ii, .-_Z41__static_initialization_and_destruction_0ii
 78  .type _GLOBAL__sub_I_addr, @function
 79 _GLOBAL__sub_I_addr:
 80 .LFB2:
 81  .loc 1 7 0
 82  .cfi_startproc
 83  pushl %ebp
 84 .LCFI6:
 85  .cfi_def_cfa_offset 8
 86  .cfi_offset 5, -8
 87  movl %esp, %ebp
 88 .LCFI7:
 89  .cfi_def_cfa_register 5
 90  subl $8, %esp
 91  .loc 1 7 0
 92  movl $65535, 4(%esp)
 93  movl $1, (%esp)
 94  call _Z41__static_initialization_and_destruction_0ii
 95  leave
 96 .LCFI8:
 97  .cfi_restore 5
 98  .cfi_def_cfa 4, 4
 99  ret
100  .cfi_endproc
101 .LFE2:
102  .size _GLOBAL__sub_I_addr, .-_GLOBAL__sub_I_addr
103  .section .init_array,"aw"
104  .align 4
105  .long _GLOBAL__sub_I_addr
106  .text
107 .Letext0:
108  .section .debug_info,"",@progbits
109 .Ldebug_info0:
110  .long 0xbd
111  .value 0x2
112  .long .Ldebug_abbrev0
113  .byte 0x4
114  .uleb128 0x1
115  .long .LASF4
116  .byte 0x4
117  .long .LASF5
118  .long .LASF6
119  .long .Ltext0
120  .long .Letext0
121  .long .Ldebug_line0
122  .uleb128 0x2
123  .byte 0x1
124  .long .LASF7
125  .byte 0x1
126  .byte 0x4
127  .long .LASF8
128  .long 0x42
129  .long .LFB0
130  .long .LFE0
131  .long .LLST0
132  .byte 0x1
133  .uleb128 0x3
134  .byte 0x4
135  .byte 0x5
136  .string "int"
137  .uleb128 0x4
138  .long .LASF9
139  .byte 0x1
140  .long .LFB1
141  .long .LFE1
142  .long .LLST1
143  .byte 0x1
144  .long 0x7d
145  .uleb128 0x5
146  .long .LASF0
147  .byte 0x1
148  .byte 0x7
149  .long 0x42
150  .byte 0x2
151  .byte 0x91
152  .sleb128 0
153  .uleb128 0x5
154  .long .LASF1
155  .byte 0x1
156  .byte 0x7
157  .long 0x42
158  .byte 0x2
159  .byte 0x91
160  .sleb128 4
161  .byte 0
162  .uleb128 0x6
163  .long .LASF10
164  .byte 0x1
165  .long .LFB2
166  .long .LFE2
167  .long .LLST2
168  .byte 0x1
169  .uleb128 0x7
170  .long .LASF2
171  .byte 0x1
172  .byte 0x1
173  .long 0x42
174  .byte 0x1
175  .byte 0x5
176  .byte 0x3
177  .long addr
178  .uleb128 0x8
179  .string "vb"
180  .byte 0x1
181  .byte 0x2
182  .long 0xb3
183  .byte 0x1
184  .byte 0x5
185  .byte 0x3
186  .long vb
187  .uleb128 0x9
188  .byte 0x4
189  .long 0xb9
190  .uleb128 0xa
191  .byte 0x1
192  .byte 0x6
193  .long .LASF3
194  .byte 0
195  .section .debug_abbrev,"",@progbits
196 .Ldebug_abbrev0:
197  .uleb128 0x1
198  .uleb128 0x11
199  .byte 0x1
200  .uleb128 0x25
201  .uleb128 0xe
202  .uleb128 0x13
203  .uleb128 0xb
204  .uleb128 0x3
205  .uleb128 0xe
206  .uleb128 0x1b
207  .uleb128 0xe
208  .uleb128 0x11
209  .uleb128 0x1
210  .uleb128 0x12
211  .uleb128 0x1
212  .uleb128 0x10
213  .uleb128 0x6
214  .byte 0
215  .byte 0
216  .uleb128 0x2
217  .uleb128 0x2e
218  .byte 0
219  .uleb128 0x3f
220  .uleb128 0xc
221  .uleb128 0x3
222  .uleb128 0xe
223  .uleb128 0x3a
224  .uleb128 0xb
225  .uleb128 0x3b
226  .uleb128 0xb
227  .uleb128 0x2007
228  .uleb128 0xe
229  .uleb128 0x49
230  .uleb128 0x13
231  .uleb128 0x11
232  .uleb128 0x1
233  .uleb128 0x12
234  .uleb128 0x1
235  .uleb128 0x40
236  .uleb128 0x6
237  .uleb128 0x2117
238  .uleb128 0xc
239  .byte 0
240  .byte 0
241  .uleb128 0x3
242  .uleb128 0x24
243  .byte 0
244  .uleb128 0xb
245  .uleb128 0xb
246  .uleb128 0x3e
247  .uleb128 0xb
248  .uleb128 0x3
249  .uleb128 0x8
250  .byte 0
251  .byte 0
252  .uleb128 0x4
253  .uleb128 0x2e
254  .byte 0x1
255  .uleb128 0x3
256  .uleb128 0xe
257  .uleb128 0x34
258  .uleb128 0xc
259  .uleb128 0x11
260  .uleb128 0x1
261  .uleb128 0x12
262  .uleb128 0x1
263  .uleb128 0x40
264  .uleb128 0x6
265  .uleb128 0x2117
266  .uleb128 0xc
267  .uleb128 0x1
268  .uleb128 0x13
269  .byte 0
270  .byte 0
271  .uleb128 0x5
272  .uleb128 0x5
273  .byte 0
274  .uleb128 0x3
275  .uleb128 0xe
276  .uleb128 0x3a
277  .uleb128 0xb
278  .uleb128 0x3b
279  .uleb128 0xb
280  .uleb128 0x49
281  .uleb128 0x13
282  .uleb128 0x2
283  .uleb128 0xa
284  .byte 0
285  .byte 0
286  .uleb128 0x6
287  .uleb128 0x2e
288  .byte 0
289  .uleb128 0x3
290  .uleb128 0xe
291  .uleb128 0x34
292  .uleb128 0xc
293  .uleb128 0x11
294  .uleb128 0x1
295  .uleb128 0x12
296  .uleb128 0x1
297  .uleb128 0x40
298  .uleb128 0x6
299  .uleb128 0x2116
300  .uleb128 0xc
301  .byte 0
302  .byte 0
303  .uleb128 0x7
304  .uleb128 0x34
305  .byte 0
306  .uleb128 0x3
307  .uleb128 0xe
308  .uleb128 0x3a
309  .uleb128 0xb
310  .uleb128 0x3b
311  .uleb128 0xb
312  .uleb128 0x49
313  .uleb128 0x13
314  .uleb128 0x3f
315  .uleb128 0xc
316  .uleb128 0x2
317  .uleb128 0xa
318  .byte 0
319  .byte 0
320  .uleb128 0x8
321  .uleb128 0x34
322  .byte 0
323  .uleb128 0x3
324  .uleb128 0x8
325  .uleb128 0x3a
326  .uleb128 0xb
327  .uleb128 0x3b
328  .uleb128 0xb
329  .uleb128 0x49
330  .uleb128 0x13
331  .uleb128 0x3f
332  .uleb128 0xc
333  .uleb128 0x2
334  .uleb128 0xa
335  .byte 0
336  .byte 0
337  .uleb128 0x9
338  .uleb128 0xf
339  .byte 0
340  .uleb128 0xb
341  .uleb128 0xb
342  .uleb128 0x49
343  .uleb128 0x13
344  .byte 0
345  .byte 0
346  .uleb128 0xa
347  .uleb128 0x24
348  .byte 0
349  .uleb128 0xb
350  .uleb128 0xb
351  .uleb128 0x3e
352  .uleb128 0xb
353  .uleb128 0x3
354  .uleb128 0xe
355  .byte 0
356  .byte 0
357  .byte 0
358  .section .debug_loc,"",@progbits
359 .Ldebug_loc0:
360 .LLST0:
361  .long .LFB0-.Ltext0
362  .long .LCFI0-.Ltext0
363  .value 0x2
364  .byte 0x74
365  .sleb128 4
366  .long .LCFI0-.Ltext0
367  .long .LCFI1-.Ltext0
368  .value 0x2
369  .byte 0x74
370  .sleb128 8
371  .long .LCFI1-.Ltext0
372  .long .LCFI2-.Ltext0
373  .value 0x2
374  .byte 0x75
375  .sleb128 8
376  .long .LCFI2-.Ltext0
377  .long .LFE0-.Ltext0
378  .value 0x2
379  .byte 0x74
380  .sleb128 4
381  .long 0
382  .long 0
383 .LLST1:
384  .long .LFB1-.Ltext0
385  .long .LCFI3-.Ltext0
386  .value 0x2
387  .byte 0x74
388  .sleb128 4
389  .long .LCFI3-.Ltext0
390  .long .LCFI4-.Ltext0
391  .value 0x2
392  .byte 0x74
393  .sleb128 8
394  .long .LCFI4-.Ltext0
395  .long .LCFI5-.Ltext0
396  .value 0x2
397  .byte 0x75
398  .sleb128 8
399  .long .LCFI5-.Ltext0
400  .long .LFE1-.Ltext0
401  .value 0x2
402  .byte 0x74
403  .sleb128 4
404  .long 0
405  .long 0
406 .LLST2:
407  .long .LFB2-.Ltext0
408  .long .LCFI6-.Ltext0
409  .value 0x2
410  .byte 0x74
411  .sleb128 4
412  .long .LCFI6-.Ltext0
413  .long .LCFI7-.Ltext0
414  .value 0x2
415  .byte 0x74
416  .sleb128 8
417  .long .LCFI7-.Ltext0
418  .long .LCFI8-.Ltext0
419  .value 0x2
420  .byte 0x75
421  .sleb128 8
422  .long .LCFI8-.Ltext0
423  .long .LFE2-.Ltext0
424  .value 0x2
425  .byte 0x74
426  .sleb128 4
427  .long 0
428  .long 0
429  .section .debug_aranges,"",@progbits
430  .long 0x1c
431  .value 0x2
432  .long .Ldebug_info0
433  .byte 0x4
434  .byte 0
435  .value 0
436  .value 0
437  .long .Ltext0
438  .long .Letext0-.Ltext0
439  .long 0
440  .long 0
441  .section .debug_line,"",@progbits
442 .Ldebug_line0:
443  .section .debug_str,"MS",@progbits,1
444 .LASF10:
445  .string "_GLOBAL__sub_I_addr"
446 .LASF6:
447  .string "/home/descent/test/simple_os.test/cpp_runtime/global_object/dos_cpp"
448 .LASF2:
449  .string "addr"
450 .LASF8:
451  .string "_Z8cpp_mainv"
452 .LASF1:
453  .string "__priority"
454 .LASF4:
455  .string "GNU C++ 4.7.1"
456 .LASF7:
457  .string "cpp_main"
458 .LASF5:
459  .string "cppb.c"
460 .LASF3:
461  .string "char"
462 .LASF0:
463  .string "__initialize_p"
464 .LASF9:
465  .string "__static_initialization_and_destruction_0"
466  .ident "GCC: (Ubuntu/Linaro 4.7.1-3lucid3) 4.7.1"
467  .section .note.GNU-stack,"",@progbits

103  .section .init_array,"aw"

g++ 4.7 產生的 _GLOBAL__sub_XXX 我終於知道放在那個 section, 就是 .init_array。所以我們還得要有 c++ runtime 來執行這段程式, 就和之前的文章一樣。

回到原問題, 這是 c 程式, 還是 c++ 程式呢? 毫無疑問, 這是 c++ 程式, 看起來很當然爾的行為, 在 c 和 c++ 是不同的。

c++ 為了這個彈性, 所以程式碼變大了, 也就有人說 c++ 比 c 大並且還要慢, 使用這樣的好處, 的確要付出這樣的代價, 但 ... 你可以不要用阿, c++ 給了你選擇的機會。

彈性和效率, 你要選那一邊?

c++ 很神奇, 也很邪惡吧?

為什麼會注意到這個, 因為我不小心中招了, global object 在完成建構前不能相互呼叫。

ex:

Io io;
Ab ab;
int addr;
char *vb = (char*)(0xb8000+160 - addr);

int main()
{
// 到這裡才可以用 vb 
}

不能在 io 使用 Ab 的相關 function (反之亦然), 因為 ctor 的順序並沒有保證 io, ab 那個先執行, 而我在 io ctor 使用了 vb 變數, 結果一直出錯, 因為我不知道 vb 被當成 global object 來對待。我花了幾天終於搞懂出問題的原因。

這個錯誤是這樣的:
我的 c++ runtime 會去呼叫 global_XXX, 所以 Io::Io() 就會被執行, 而 Io::Io() 用到 vb, 但是這時候 _GLOBAL__sub_I_addr 還沒執行到, 所以 vb 的值不是我想的那個值, 因此就得到了錯誤結果。

結論就是 global variable 請當成 global object 來看待, 在互用時注意是否已經被建構起來。

一直以來認為理所當然的寫法竟然有著這樣的魔法, 在誤打誤撞的狀況下弄懂了也算好事一樁。

那能不能保證 ctor 執行順序呢? 這應該很難, 不容易做到, 要不然 c++ specification 應該會定出來。



感謝 ptt  loveme00835 提供 __attribute__ init_priority:
http://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Attributes.html

但是我這樣寫
extern u8 *vb __attribute__ ((init_priority (200)));
得到錯誤訊息:
tool.cpp:6: error: can only use ‘init_priority’ attribute on file-scope definitions of objects of class type

ptt  akasan:
要保證 global ctor 的順序的話, 放在 function local static 為常見作法:
http://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Attributes.html

我用了 akasan 的方式:

u8 *get_vb()
{
  static u8 *vb = (u8*)(0xb8000+160 - load_addr);   
  return vb;
}

這樣就不會有 g++ 就不會產生 z41__static_initialization_and_destruction_0ii, _GLOBAL__sub_I_addr 這兩個 function, 也不會把 vb 當 global object 的方式來處理。細節可從反組譯內容比較得知。這其實就是手動喚起 ctor 而不靠 c++ runtime 來處理 ctor。

沒有留言:

張貼留言

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。