C 的空类为什么占一个字节

情景分析

#include 


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 
: 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 1b: b8 00 00 00 00 mov $0x0,%eax 20: e8 00 00 00 00 callq 25 25: b8 00 00 00 00 mov $0x0,%eax 2a: c9 leaveq 2b: c3 retq

g++ 得到可执行文件(动态链接)后,通过objdump -ds查看

000000000000078a 
: 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 7af: b8 00 00 00 00 mov $0x0,%eax 7b4: c9 leaveq 7b5: c3 retq

可知关于sizeof(A)处的代码,很早的时候就替换成常量0x1。

 

Windows

 

#include 

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个字节内存,刚好也和本文探讨的内容对应,是不是很神奇呢?

你可能感兴趣的:(编程语言,内存分析)