情景分析
#include <stdio.h>
class A {};
int main(int argc, char *argv[])
{
printf("%ld\n", sizeof(A));
return 0;
}
Linux端
g++ -S下得到的彙編(部分代碼)
.file "emptyclass.cpp"
.text
.section .rodata
.type _ZStL19piecewise_construct, @object
.size _ZStL19piecewise_construct, 1
_ZStL19piecewise_construct:
.zero 1
.local _ZStL8__ioinit
.comm _ZStL8__ioinit,1,1
.LC0:
.string "%ld\n"
.text
.globl main
.type main, @function
main:
.LFB1493:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl %edi, -4(%rbp)
movq %rsi, -16(%rbp)
movl $1, %esi
leaq .LC0(%rip), %rdi
movl $0, %eax
call printf@PLT
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
g++ -c 得到的目標文件,通過objdump -ds查看
0000000000000000 <main>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 83 ec 10 sub $0x10,%rsp
8: 89 7d fc mov %edi,-0x4(%rbp)
b: 48 89 75 f0 mov %rsi,-0x10(%rbp)
f: be 01 00 00 00 mov $0x1,%esi
14: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 1b <main+0x1b>
1b: b8 00 00 00 00 mov $0x0,%eax
20: e8 00 00 00 00 callq 25 <main+0x25>
25: b8 00 00 00 00 mov $0x0,%eax
2a: c9 leaveq
2b: c3 retq
g++ 得到可執行文件(動態鏈接)後,通過objdump -ds查看
000000000000078a <main>:
78a: 55 push %rbp
78b: 48 89 e5 mov %rsp,%rbp
78e: 48 83 ec 10 sub $0x10,%rsp
792: 89 7d fc mov %edi,-0x4(%rbp)
795: 48 89 75 f0 mov %rsi,-0x10(%rbp)
799: be 01 00 00 00 mov $0x1,%esi
79e: 48 8d 3d 00 01 00 00 lea 0x100(%rip),%rdi # 8a5 <_ZStL19piecewise_construct+0x1>
7a5: b8 00 00 00 00 mov $0x0,%eax
7aa: e8 91 fe ff ff callq 640 <printf@plt>
7af: b8 00 00 00 00 mov $0x0,%eax
7b4: c9 leaveq
7b5: c3 retq
可知關於sizeof(A)處的代碼,很早的時候就替換成常量0x1。
Windows
#include <stdio.h>
class A {};
int main(int argc, char *argv[])
{
00AD13C0 push ebp
00AD13C1 mov ebp,esp
00AD13C3 sub esp,0C0h
00AD13C9 push ebx
00AD13CA push esi
00AD13CB push edi
00AD13CC lea edi,[ebp-0C0h]
00AD13D2 mov ecx,30h
00AD13D7 mov eax,0CCCCCCCCh
00AD13DC rep stos dword ptr es:[edi]
printf("%ld\n", sizeof(A));
00AD13DE mov esi,esp
00AD13E0 push 1
00AD13E2 push 0AD5858h
00AD13E7 call dword ptr ds:[0AD9114h]
00AD13ED add esp,8
00AD13F0 cmp esi,esp
00AD13F2 call __RTC_CheckEsp (0AD1136h)
return 0;
00AD13F7 xor eax,eax
}
00AD13F9 pop edi
00AD13FA pop esi
00AD13FB pop ebx
00AD13FC add esp,0C0h
00AD1402 cmp ebp,esp
00AD1404 call __RTC_CheckEsp (0AD1136h)
00AD1409 mov esp,ebp
00AD140B pop ebp
00AD140C ret
結論
sizeof只是個運算符,它在彙編時就會被編譯器替換成值。
也就是說,編譯器決定sizeof中內容的佔用內存大小。
在這個過程中,編譯器有如下規則。
- 類型本身不佔用內存,而是實例化後的對象佔用內存。所以sizeof中的內容不論是什麼,都要理解成是個實例
- 實例的內存大小至少爲其非靜態成員的大小總和。
- 編譯器什麼時候會額外分配內存給對象呢?
- 虛函數指針。
- 內存對齊。以CPU字長的整數倍讀取數據會更快。
- 空的stuct、union、class的實例都至少佔用1字節。C/C++標準都如此。
擴展
佔用空間爲0的對象,其意義和malloc(0)相對應。
能不能支持大小爲0的對象,就看操作系統對malloc(0)的反應:有的會返回NULL,表示申請失敗;有的返回一個貌似正常的指針,但是這個指針所致內存並不有效。和編譯器一樣,大多數運行庫爲了保證廣泛的可移植性,會對malloc做一層包裝,以Python爲例
#define PyMem_MALLOC(n) ((size_t)(n) > (size_t)PY_SSIZE_T_MAX ? NULL \
: malloc(((n) != 0) ? (n) : 1))
#define PyMem_REALLOC(p, n) ((size_t)(n) > (size_t)PY_SSIZE_T_MAX ? NULL \
: realloc((p), ((n) != 0) ? (n) : 1))
#define PyMem_FREE free
它保證了對malloc調用至少會申請1個字節內存,剛好也和本文探討的內容對應,是不是很神奇呢?