C 的空類爲什麼佔一個字節

情景分析

#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中內容的佔用內存大小。

在這個過程中,編譯器有如下規則。

  1. 類型本身不佔用內存,而是實例化後的對象佔用內存。所以sizeof中的內容不論是什麼,都要理解成是個實例
  2. 實例的內存大小至少爲其非靜態成員的大小總和。
  3. 編譯器什麼時候會額外分配內存給對象呢?
    1. 虛函數指針。
    2. 內存對齊。以CPU字長的整數倍讀取數據會更快。
    3. 空的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個字節內存,剛好也和本文探討的內容對應,是不是很神奇呢?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章