一个对象至少占几个字节?

如题所示

一个对象至少占几个字节呢?

我们分别使用sizeof、class_getInstanceSize、malloc_size三个函数方法进行操作:

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *obj = [[NSObject alloc] init];
        
        NSLog(@"obj_sizeof - %d", sizeof(obj));
        NSLog(@"obj_class_getInstanceSize - %d", class_getInstanceSize([NSObject class]));
        NSLog(@"obj_malloc_size - %d", malloc_size((__bridge const void *)(obj)));
    }
    return 0;
}

打印结果为:

2019-04-01 15:33:26.457385+0800 01-内存分析[5294:290097] obj_sizeof - 8
2019-04-01 15:33:26.457619+0800 01-内存分析[5294:290097] obj_class_getInstanceSize - 8
2019-04-01 15:33:26.457651+0800 01-内存分析[5294:290097] obj_malloc_size - 16

可以看出,sizeof、class_getInstanceSize返回的obj占8个字节,malloc_size返回obj占16个字节。

那么,为什么打印出结果不一样呢?它们都是什么意思呢?

首先,sizeof(A)不是函数,而是编译器特性,是一个运算符,在编译期间就可以确定A类型所占的字节大小。由于我们放入的是obj对象,而obj是一个指向对象的指针,因此,sizeof返回的8是obj指针所占的字节大小。而如果放入的是[NSObject class],返回什么呢?为什么呢?

class_getInstanceSize的底层究竟是什么呢?
点击进去在runtime.h文件中发现class_getInstanceSize的定义为:
在这里插入图片描述
好吧,好像什么也看不出来,那么只能通过底层开源代码查看class_getInstanceSize的实现方式了。那么

如何获取苹果底层开源代码呢?

可以通过苹果开源代码,点击objc4文件夹,下载最新的objc4-xxx.tar.gz,解压后打开项目,在项目里面搜索class_getInstanceSize,可以在objc-class.mm文件中看到:
在这里插入图片描述

alignedInstanceSize

点击alignedInstanceSize()可以看到:
在这里插入图片描述
返回的是一个类Class的一个成员变量ivar的大小。
因此,可以看出class_getInstanceSize返回的是一个类中成员变量所占的大小


malloc_size

点击malloc_size,进入可以看到如下代码:
在这里插入图片描述
可以看出,malloc_size通过输入一个指针ptr,可以返回指针所指向的内容的大小,malloc_size返回结果是一个对象实际分配的空间大小

由此可见:

一个对象obj分配了16个字节,通过《iOS中类、对象的本质》可知,一个对象obj中包含一个Class类型的指针isa,而根据《一个指针占几个字节?原理是什么呢?》可知,一个指针占8个字节(在64位电脑上),也就是Class类型的isa指针所占的8个字节大小。

那么,为什么一个对象分配了16个字节,对象只用了8个字节呢?

我们知道NSObject *obj = [[NSObject alloc] init];中alloc其实是调用的是+ (instancetype)allocWithZone:(struct _NSZone *)zone;方法,而通过上面我们下周的底层开源代码可以发现allocWithZone调用的是:
在这里插入图片描述继续点击,可以发现_objc_rootAllocWithZone的实现:
在这里插入图片描述在这里面有一个class_createInstance函数,点击进入:
在这里插入图片描述
再次点击_class_createInstanceFromZone进入:
在这里插入图片描述里面有一个obj = (id)calloc(1, size);函数,是创建obj的,而size是由size_t size = cls->instanceSize(extraBytes);返回的。点击instanceSize进入可以发现:
在这里插入图片描述这里面规定了if (size < 16) size = 16;即,如果分配的size小于16,那么就让size=16。也就是,一个对象至少分配16个字节。

另外,我们也可以通过查看obj的内存地址,通过内存地址找到所占用的二进制,查看obj到底是占用几个字节

通过打断点,我们可以找到obj的内存地址0x102800930,在Debug-Debug Workflow -View Memory
在这里插入图片描述
把内存0x102800930填写在下列框中:
在这里插入图片描述
可以看到其二进制内容:
41 61 88 9C FF FF 1D 00 00 00 00 00 00 00 00 00
该二进制是用16进制表示的,也就是一个数字是4个2进制,两个数字整好是8个二进制,也就是一个字节。通过观察可以看出,该二进制共有16个字节,但是,只有8个字节有内容,存放obj的内容,其余8个字节虽然分配了,但是并没有使用。

在 Protocol Buffers(Protobuf)中,判断一个 **空的 Protobuf 对象**(即所有字段都未设置)序列化后用多少字节,需要理解 Protobuf 的编码机制。我们先明确几个概念: --- ### ✅ 什么是“空”的 Protobuf 对象? - 所有字段都没有被显式赋值。 - 使用默认构造函数创建的对象。 - 每个字段使用其 **默认值**(如 `0`、`""`、`false` 等),但这些默认值 **不会被序列化**。 > ⚠️ 关键点:**Protobuf 只序列化“已设置”的非默认值字段**。如果一个字段是默认值或未设置,则不会写入二进制流。 因此,**一个完全为空的 Protobuf 消息对象,序列化后的大小为 0 字节**。 --- ### 🧪 示例说明 假设你有一个 `.proto` 文件定义如下: ```protobuf message Person { string name = 1; int32 id = 2; bool active = 3; } ``` 创建并序列化一个对象: ```cpp Person person; // 所有字段都是默认值:name="", id=0, active=false std::string output; person.SerializeToString(&output); std::cout << "Serialized size: " << output.size() << " bytes" << std::endl; // 输出:Serialized size: 0 bytes ``` ✅ 结果:输出是 `0` 字节! --- ### 💡 那么这个对象在内存中多少字节?(与序列化无关) 注意区分两个问题: | 问题 | 回答 | |------|------| | **序列化后多少字节?** | `0` 字节(因为没有字段需要编码) | | **C++ 对象在内存中多少字节?** | 多个因素决定,通常几十到上百字节 | #### 内存用分析(C++ 层面) 即使消息“逻辑上为空”,它的 C++ 实例仍会用内存,用于存储: - 各字段的存储空间(比如 `std::string` 至少有指针和小字符串优化缓冲区) - 是否设置过字段的位图(`has_bits_`) - 锁或其他内部同步结构(取决于版本) - 虚函数表指针(继承自 `Message` 基类) 你可以用 `sizeof(person)` 查看栈上对象大小: ```cpp std::cout << "Size of Person object in memory: " << sizeof(Person) << " bytes" << std::endl; ``` 示例输出(不同编译器/平台可能不同): ``` Size of Person object in memory: 48 bytes ``` 但这 **不代表序列化大小**!这只是运行时内存开销。 --- ### 🔍 序列化大小何时大于 0? 只有当你设置了非默认值字段时,才会产生数据: ```cpp person.set_name("Alice"); // 非空字符串 person.set_id(123); // 非零整数 // active 保持 false 不设置 std::string output; person.SerializeToString(&output); std::cout << "Serialized size: " << output.size() << " bytes" << std::endl; ``` 计算方式(基于 [Varint + Length Delimiter 编码](https://developers.google.com/protocol-buffers/docs/encoding)): - `name`: tag (1 → 1 byte) + length prefix + "Alice" (5) - tag+type: `(field_number << 3) \| wire_type` = `(1 << 3) \| 2` = `10` → varint 编码为 `0x0A` - length: 5 → `0x05` - value: `"Alice"` → `0x41 6C 69 63 65` - total: 1 + 1 + 5 = 7 bytes - `id`: tag (2 → `0x10`) + varint(123) → `0x10 0x7B` → 2 bytes - 总计:7 + 2 = **9 bytes** 所以输出应为 `9` 字节。 --- ### ✅ 总结回答你的问题: > **怎么判断一个空的 Protobuf 对象需要多少字节?** #### 分情况回答: | 类型 | 字节数 | 解释 | |------|-----------|------| | **序列化后(传输/存储大小)** | `0` 字节 | 没有任何字段被设置,Protobuf 不编码默认值 | | **内存中(C++ 对象大小)** | `sizeof(MessageType)`(例如 32~100+ 字节) | 包含字符串对象、has_bits、虚表等运行时结构 | --- ### ✅ 如何验证? ```cpp Person person; std::string buffer; person.SerializeToString(&buffer); size_t serialized_size = buffer.size(); // 通常是 0 size_t byte_size_long = person.ByteSizeLong(); // 同样返回 0 std::cout << "Serialized size: " << serialized_size << " bytes\n"; std::cout << "ByteSizeLong(): " << byte_size_long << " bytes\n"; std::cout << "Memory size (sizeof): " << sizeof(Person) << " bytes\n"; ``` 输出示例: ``` Serialized size: 0 bytes ByteSizeLong(): 0 bytes Memory size (sizeof): 48 bytes ``` ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值