现代 C++ 跨平台开发-内存篇:多平台跨层调用场景的内存管理
本文是整个【现代 C++ 跨平台开发-内存篇】系列的第 8 篇,主要涉及:多平台跨层调用场景的内存管理。
- 现代 C++ 跨平台开发-内存篇:类型转换、重载决议、内存模型
- 现代 C++ 跨平台开发-内存篇:内存管理、智能指针
- 现代 C++ 跨平台开发-内存篇:字节序、内存对齐、内存布局、虚函数表与多态
- 现代 C++ 跨平台开发-内存篇:拷贝、移动、可重定位
- 现代 C++ 跨平台开发-内存篇:函数调用、异常处理、异步等场景的内存管理
- 现代 C++ 跨平台开发-内存篇:STL 内存管理
- 现代 C++ 跨平台开发-内存篇:内存一致性、未定义行为、可观测性
- 现代 C++ 跨平台开发-内存篇:多平台跨层调用场景的内存管理
- 现代 C++ 跨平台开发-内存篇:内存问题分析工具
JNI 内存管理
JNI 的世界里,要摒弃 GC 搞定一切的思维。
引用
本地引用:
只有返回
jobject的函数才会产生本地引用:方法和字段查找返回的
jmethodID/jfieldID等 ID 类型都不会产生本地引用;类查找返回的
jclass属于本地引用;
循环或回调等频繁调用场景,本地引用需显式释放;
也可通过
PushLocalFrame()/PopLocalFrame()自动管理;
全局引用:
- 用于延长生命周期,需要在合适的时机手动释放。
源码分析
数据结构
class JNIEnvExt : public JNIEnv {
// Cookie used when using the local indirect reference table.
jni::LRTSegmentState local_ref_cookie_;
// JNI local references.
jni::LocalReferenceTable locals_;
// Stack of cookies corresponding to PushLocalFrame/PopLocalFrame calls.
// TODO: to avoid leaks (and bugs), we need to clear this vector on entry (or return)
// to a native method.
std::vector<jni::LRTSegmentState> stacked_local_ref_cookies_;
}
// The state of the current segment contains the top index.
struct LRTSegmentState {
uint32_t top_index;
};
class LocalReferenceTable {
/// semi-public - read/write by jni down calls.
LRTSegmentState segment_state_;
//...
// The singly-linked list of free nodes.
// We use entry indexes instead of pointers and `kFreeListEnd` instead of null indicates
// the end of the list. See `LocalReferenceTable::GetEntry()` and `LrtEntry::GetNextFree().
//
// We use the lowest bit to record whether CheckJNI is enabled. This helps us
// check that the list is empty and CheckJNI is disabled in a single comparison.
uint32_t free_entries_list_;
// Individual tables.
// As long as we have only one small table, we use `small_table_` to avoid an extra load
// from another heap allocated location, otherwise we set it to null and use `tables_`.
LrtEntry* small_table_; // For optimizing the fast-path.
dchecked_vector<LrtEntry*> tables_;
//...
};
class LrtEntry {
// We record the contents as a `GcRoot<>` but it is an actual `GcRoot<>` only if it's below
// the current segment's top index, it's not a "serial number" or inactive entry in a CheckJNI
// chunk, and it's not marked as "free". Such entries are never null.
GcRoot<mirror::Object> root_;
};
类/方法/字段查找
static jclass FindClass(JNIEnv* env, const char* name) {
//...
Runtime* runtime = Runtime::Current();
ClassLinker* class_linker = runtime->GetClassLinker();
std::string descriptor(NormalizeJniClassDescriptor(name));
ScopedObjectAccess soa(env);
ObjPtr<mirror::Class> c = nullptr;
if (runtime->IsStarted()) {
StackHandleScope<1> hs(soa.Self());
Handle<mirror::ClassLoader> class_loader(hs.NewHandle(GetClassLoader<kEnableIndexIds>(soa)));
c = class_linker->FindClass(soa.Self(), descriptor.c_str(), class_loader);
} else {
c = class_linker->FindSystemClass(soa.Self(), descriptor.c_str());
}
return soa.AddLocalReference<jclass>(c);
}
static jmethodID GetMethodID(JNIEnv* env, jclass java_class, const char* name, const char* sig) {
//...
ScopedObjectAccess soa(env);
return FindMethodID<kEnableIndexIds>(soa, java_class, name, sig, false);
}
static jfieldID GetFieldID(JNIEnv* env, jclass java_class, const char* name, const char* sig) {
//...
ScopedObjectAccess soa(env);
return FindFieldID<kEnableIndexIds>(soa, java_class, name, sig, false);
}
template<bool kEnableIndexIds>
static jmethodID FindMethodID(ScopedObjectAccess& soa, jclass jni_class, const char* name, const char* sig, bool is_static) REQUIRES_SHARED(Locks::mutator_lock_) {
return jni::EncodeArtMethod<kEnableIndexIds>(FindMethodJNI(soa, jni_class, name, sig, is_static));
}
template<bool kEnableIndexIds>
static jfieldID FindFieldID(const ScopedObjectAccess& soa, jclass jni_class, const char* name, const char* sig, bool is_static) REQUIRES_SHARED(Locks::mutator_lock_) {
return jni::EncodeArtField<kEnableIndexIds>(FindFieldJNI(soa, jni_class, name, sig, is_static));
}
ArtMethod* FindMethodJNI(const ScopedObjectAccess& soa, jclass jni_class, const char* name, const char* sig, bool is_static) {
ObjPtr<mirror::Class> c = EnsureInitialized(soa.Self(), soa.Decode<mirror::Class>(jni_class));
//...
ArtMethod* method = nullptr;
auto pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
if (c->IsInterface()) {
method = c->FindInterfaceMethod(name, sig, pointer_size);
} else {
method = c->FindClassMethod(name, sig, pointer_size);
}
//...
return method;
}
ArtField* FindFieldJNI(const ScopedObjectAccess& soa, jclass jni_class, const char* name, const char* sig, bool is_static) {
//...
ArtField* field = nullptr;
ObjPtr<mirror::Class> field_type;
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
//...
if (sig[1] != '\0') {
Handle<mirror::ClassLoader> class_loader(hs.NewHandle(c->GetClassLoader()));
field_type = class_linker->FindClass(soa.Self(), sig, class_loader);
} else {
field_type = class_linker->FindPrimitiveClass(*sig);
}
//...
if (is_static) {
field = c->FindStaticField(name, field_type->GetDescriptor(&temp));
} else {
field = c->FindInstanceField(name, field_type->GetDescriptor(&temp));
}
//...
return field;
}
可以看到,类查找会调用 AddLocalReference(),而方法和字段查找不会。
LocalReference 管理
jobject JNIEnvExt::NewLocalRef(mirror::Object* obj) {
//...
jobject ref = reinterpret_cast<jobject>(locals_.Add(local_ref_cookie_, obj, &error_msg));
//...
return ref;
}
void JNIEnvExt::DeleteLocalRef(jobject obj) {
if (obj != nullptr) {
locals_.Remove(local_ref_cookie_, reinterpret_cast<IndirectRef>(obj));
}
}
IndirectRef LocalReferenceTable::Add(LRTSegmentState previous_state, ObjPtr<mirror::Object> obj, std::string* error_msg) {
//...
auto store_obj = [obj, this](LrtEntry* free_entry, const char* tag) {
free_entry->SetReference(obj);
IndirectRef result = ToIndirectRef(free_entry);
//...
return result;
};
//...
LrtEntry* free_entry = GetEntry(top_index);
segment_state_.top_index = top_index + 1u;
return store_obj(free_entry, "slow-path");
}
bool LocalReferenceTable::Remove(LRTSegmentState previous_state, IndirectRef iref) {
//...
LrtEntry* entry = ToLrtEntry(iref);
uint32_t entry_index = GetReferenceEntryIndex(iref);
//...
// Prune the free entry list if a segment with holes was popped before the `Remove()` call.
uint32_t first_free_index = GetFirstFreeIndex();
if (first_free_index != kFreeListEnd && first_free_index >= top_index) {
PrunePoppedFreeEntries([&](size_t index) { return GetEntry(index); });
}
//...
if (is_top_entry) {
// Top-most entry. Scan up and consume holes created with the current CheckJNI setting.
constexpr uint32_t kDeadLocalValue = 0xdead10c0;
entry->SetReference(reinterpret_cast32<mirror::Object*>(kDeadLocalValue));
// TODO: Maybe we should not prune free entries from the top of the segment
// because it has quadratic worst-case complexity. We could still prune while
// the first free list entry is at the top.
uint32_t prune_start = prune_end;
size_t prune_count;
auto find_prune_range = [&](size_t chunk_size, auto is_prev_entry_free) {
while (prune_start > bottom_index && is_prev_entry_free(prune_start)) {
prune_start -= chunk_size;
}
prune_count = (prune_end - prune_start) / chunk_size;
};
if (prune_count != 0u) {
// Remove pruned entries from the free list.
size_t remaining = prune_count;
uint32_t free_index = GetFirstFreeIndex();
while (remaining != 0u && free_index >= prune_start) {
//...
LrtEntry* pruned_entry = GetEntry(free_index);
free_index = pruned_entry->GetNextFree();
pruned_entry->SetReference(reinterpret_cast32<mirror::Object*>(kDeadLocalValue));
--remaining;
}
free_entries_list_ = FirstFreeField::Update(free_index, free_entries_list_);
while (remaining != 0u) {
//...
LrtEntry* free_entry = GetEntry(free_index);
while (free_entry->GetNextFree() < prune_start) {
free_index = free_entry->GetNextFree();
//...
free_entry = GetEntry(free_index);
}
LrtEntry* pruned_entry = GetEntry(free_entry->GetNextFree());
free_entry->SetNextFree(pruned_entry->GetNextFree());
pruned_entry->SetReference(reinterpret_cast32<mirror::Object*>(kDeadLocalValue));
--remaining;
}
//...
}
segment_state_.top_index = prune_start;
//...
} else {
// Not the top-most entry. This creates a hole.
entry->SetNextFree(GetFirstFreeIndex());
free_entries_list_ = FirstFreeField::Update(entry_index, free_entries_list_);
//...
}
return true;
}
LrtEntry* GetEntry(size_t entry_index) const {
//...
size_t table_start_index = (entry_index < kSmallLrtEntries) ? 0u : TruncToPowerOfTwo(entry_index);
size_t table_index = (entry_index < kSmallLrtEntries) ? 0u : NumTablesForSize(table_start_index);
LrtEntry* table = tables_[table_index];
return &table[entry_index - table_start_index];
}
可见:
新增和释放单个 LocalReference 都是针对当前的
local_ref_cookie_(LRTSegmentState类型,记录栈顶);并没有直接释放内存,而是标记为无效、加入空闲链表(free list):避免频繁移动内存,用 free list 复用“洞”(holes)。
Frame 管理
void JNIEnvExt::PushFrame(int capacity) {
DCHECK_GE(locals_.FreeCapacity(), static_cast<size_t>(capacity));
stacked_local_ref_cookies_.push_back(local_ref_cookie_);
local_ref_cookie_ = locals_.GetSegmentState();
}
void JNIEnvExt::PopFrame() {
locals_.SetSegmentState(local_ref_cookie_);
local_ref_cookie_ = stacked_local_ref_cookies_.back();
stacked_local_ref_cookies_.pop_back();
}
可见:
PushFrame()并没有新开辟内存:仅仅保存当前帧的“结束位置”,为后续
PopFrame()提供“回滚点”;后续 add LocalReference 时再按需分配;
PopFrame()也没有直接的内存释放逻辑:仅将局部引用表的逻辑顶部(top_index)回退到快照值;
下次 delete LocalReference 才会真正触发 free list 和 hole 更新。
PushLocalFrame()/PopLocalFrame()不是银弹:过分依赖可能造成内存释放不及时;
忘记 pop 可能造成内存泄漏(可通过 RAII 封装);
class ScopedLocalFrame {
public:
ScopedLocalFrame(JNIEnv* env, int capacity = 16) : env_(env) {
env_->PushLocalFrame(capacity);
}
~ScopedLocalFrame() { env_->PopLocalFrame(nullptr); }
private:
JNIEnv* env_;
};
数组
GetStringUTFChars()、GetIntArrayElements()、GetByteArrayElements()会返回 C 指针,需要通过ReleaseStringUTFChars()、ReleaseXXXArrayElements()显式释放;GetObjectArrayElement()返回的jobject(本地引用),无需手动释放 native 内存。
源码分析
static jobject GetObjectArrayElement(JNIEnv* env, jobjectArray java_array, jsize index) {
//...
return soa.AddLocalReference<jobject>(array->Get(index));
}
//////////
static jbyte* GetByteArrayElements(JNIEnv* env, jbyteArray array, jboolean* is_copy) {
//...
return GetPrimitiveArray<jbyteArray, jbyte*, ByteArray>(soa, array, is_copy);
}
static void ReleaseByteArrayElements(JNIEnv* env, jbyteArray array, jbyte* elements, jint mode) {
ReleasePrimitiveArray<jbyteArray, jbyte, mirror::ByteArray>(env, array, elements, mode);
}
template <typename ArrayT, typename ElementT, typename ArtArrayT>
static ElementT* GetPrimitiveArray(JNIEnv* env, ArrayT java_array, jboolean* is_copy) {
//...
ObjPtr<ArtArrayT> array = DecodeAndCheckArrayType<ArrayT, ElementT, ArtArrayT>(
soa, java_array, "GetArrayElements", "get");
//...
if (Runtime::Current()->GetHeap()->IsMovableObject(array)) {
//...
const size_t component_size = sizeof(ElementT);
size_t size = array->GetLength() * component_size;
void* data = new uint64_t[RoundUp(size, 8) / 8];
memcpy(data, array->GetData(), size);
return reinterpret_cast<ElementT*>(data);
} else {
//...
return reinterpret_cast<ElementT*>(array->GetData());
}
}
static void ReleasePrimitiveArray(ScopedObjectAccess& soa, ObjPtr<mirror::Array> array, size_t component_size, void* elements, jint mode) REQUIRES_SHARED(Locks::mutator_lock_) {
void* array_data = array->GetRawData(component_size, 0);
gc::Heap* heap = Runtime::Current()->GetHeap();
bool is_copy = array_data != elements;
size_t bytes = array->GetLength() * component_size;
if (is_copy) {
//...
if (mode != JNI_ABORT) {
memcpy(array_data, elements, bytes);
} else if (kWarnJniAbort && memcmp(array_data, elements, bytes) != 0) {
//...
}
}
if (mode != JNI_COMMIT) {
if (is_copy) {
delete[] reinterpret_cast<uint64_t*>(elements);
} else if (heap->IsMovableObject(array)) {
//...
}
}
}
可见:
数值类的 JNI 数组 getter 会调用
GetPrimitiveArray(),内部可能发生memcpy(),所以必须手动调用对应的 release 接口;JNI object 数组 getter 直接返回 Local Reference,所以一般无需手动释放。
更进一步,它是通过 IsMovableObject() 判断是否需要拷贝的:
首先判断是否使用可移动式 GC(ART 默认开启);
然后判断当前内存区域是否属于可移动。
拷贝的深层原因是:某些 GC 算法为防止内存碎片,会主动将存活对象从一个内存区域(From-space) 移动到另一个区域(To-space),无法保证 Java 堆内存地址的不变性,所以必须拷贝。
// Garbage collector constants.
static constexpr bool kMovingCollector = true;
static constexpr bool kMarkCompactSupport = false && kMovingCollector;
bool Heap::IsMovableObject(ObjPtr<mirror::Object> obj) const {
if (kMovingCollector) {
space::Space* space = FindContinuousSpaceFromObject(obj.Ptr(), true);
if (space != nullptr) {
return space->CanMoveObjects();
}
}
return false;
}
space::ContinuousSpace* Heap::FindContinuousSpaceFromObject(ObjPtr<mirror::Object> obj, bool fail_ok) const {
space::ContinuousSpace* space = FindContinuousSpaceFromAddress(obj.Ptr());
if (space != nullptr) {
return space;
}
//...
return nullptr;
}
space::ContinuousSpace* Heap::FindContinuousSpaceFromAddress(const mirror::Object* addr) const {
for (const auto& space : continuous_spaces_) {
if (space->Contains(addr)) {
return space;
}
}
return nullptr;
}
//////////
class Space {
public:
virtual bool CanMoveObjects() const = 0;
//...
};
class RegionSpace final : public ContinuousMemMapAllocSpace {
public:
bool CanMoveObjects() const override {
return true;
}
//...
};
class LargeObjectSpace : public DiscontinuousSpace, public AllocSpace {
public:
bool CanMoveObjects() const override {
return false;
}
};
高效字节传输
DirectByteBuffer 可用于Java/JNI 之间高效共享内存,它属于堆外内存(off-heap memory),不存在隐式拷贝:
如果是 Java 层分配的,GC 通过
Cleaner释放;如果是 JNI 层分配的,需要手动
free()释放。
Java 层创建:
ByteBuffer buf = ByteBuffer.allocateDirect(1024);
nativeHandleBuffer(buffer);
JNI 层使用:
NIEXPORT void JNICALL
Java_Foo_Bar_nativeHandleBuffer(JNIEnv *env, jobject obj, jobject byteBuffer) {
// 获取 DirectByteBuffer 的起始地址
void* address = (*env)->GetDirectBufferAddress(env, byteBuffer);
if (address == NULL) {
// 不是 DirectBuffer 或其他错误
return;
}
// 获取容量
jlong capacity = (*env)->GetDirectBufferCapacity(env, byteBuffer);
// 直接读写内存
memset(address, 0, capacity);
}
JNI 层创建并负责释放:
NIEXPORT jobject JNICALL
Java_Foo_Bar_createBuffer(JNIEnv *env, jclass cls, jint size) {
void* mem = malloc(size); // 或使用 mmap 等
if (!mem) return NULL;
// 创建 DirectByteBuffer 包装该内存
jobject directBuffer = (*env)->NewDirectByteBuffer(env, mem, size);
return directBuffer;
}
JNIEXPORT void JNICALL
Java_Foo_Bar_releaseBuffer(JNIEnv *env, jobject obj, jobject buffer) {
void* addr = (*env)->GetDirectBufferAddress(env, buffer);
free(addr); // 释放之前 malloc 的内存
}
源码分析:
final Cleaner cleaner;
final MemoryRef memoryRef;
DirectByteBuffer(int capacity, MemoryRef memoryRef) {
super(-1, 0, capacity, capacity, memoryRef.buffer, memoryRef.offset);
// Only have references to java objects, no need for a cleaner since the GC will do all the work.
this.memoryRef = memoryRef;
this.address = memoryRef.allocatedAddress + memoryRef.offset;
cleaner = null;
this.isReadOnly = false;
}
////////////
final static class MemoryRef {
byte[] buffer;
long allocatedAddress;
final int offset;
boolean isAccessible;
boolean isFreed;
final Object originalBufferObject;
MemoryRef(int capacity) {
VMRuntime runtime = VMRuntime.getRuntime();
buffer = (byte[]) runtime.newNonMovableArray(byte.class, capacity + 7);
allocatedAddress = runtime.addressOf(buffer);
offset = (int) (((allocatedAddress + 7) & ~(long) 7) - allocatedAddress);
isAccessible = true;
isFreed = false;
originalBufferObject = null;
}
//...
void free() {
buffer = null;
allocatedAddress = 0;
isAccessible = false;
isFreed = true;
}
}
////////////
public class Cleaner extends PhantomReference<Object> {
// Dummy reference queue
private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue<>();
// Doubly-linked list of live cleaners, which prevents the cleaners
// themselves from being GC'd before their referents
static private Cleaner first = null;
private Cleaner next = null, prev = null;
private static synchronized Cleaner add(Cleaner cl) {
if (first != null) {
cl.next = first;
first.prev = cl;
}
first = cl;
return cl;
}
//...
}
template <bool kInstrumented = true, typename PreFenceVisitor>
mirror::Object* AllocNonMovableObject(Thread* self, ObjPtr<mirror::Class> klass, size_t num_bytes, const PreFenceVisitor& pre_fence_visitor) {
mirror::Object* obj = AllocObjectWithAllocator<kInstrumented>(self, klass, num_bytes, GetCurrentNonMovingAllocator(), pre_fence_visitor);
//...
return obj;
}
AllocatorType GetCurrentNonMovingAllocator() const {
return current_non_moving_allocator_;
}
template <bool kInstrumented, bool kCheckLargeObject, typename PreFenceVisitor>
inline mirror::Object* Heap::AllocObjectWithAllocator(Thread* self, ObjPtr<mirror::Class> klass, size_t byte_count, AllocatorType allocator, const PreFenceVisitor& pre_fence_visitor) {
//...
obj = TryToAllocate<kInstrumented, false>(self, allocator, byte_count, &bytes_allocated, &usable_size, &bytes_tl_bulk_allocated);
//...
}
template <const bool kInstrumented, const bool kGrow>
inline mirror::Object* Heap::TryToAllocate(Thread* self, AllocatorType allocator_type, size_t alloc_size, size_t* bytes_allocated, size_t* usable_size, size_t* bytes_tl_bulk_allocated) {
//...
switch (allocator_type) {
//...
case kAllocatorTypeNonMoving: {
ret = non_moving_space_->Alloc(self, alloc_size, bytes_allocated, usable_size, bytes_tl_bulk_allocated);
break;
}
//...
}
//...
}
Heap::Heap(//...) {
//...
MemMap non_moving_space_mem_map;
if (separate_non_moving_space) {
//...
if (heap_reservation.IsValid()) {
non_moving_space_mem_map = heap_reservation.RemapAtEnd(heap_reservation.Begin(), space_name, PROT_READ | PROT_WRITE, &error_str);
} else {
non_moving_space_mem_map = MapAnonymousPreferredAddress(space_name, request_begin, non_moving_space_capacity, &error_str);
}
//...
non_moving_space_ = space::DlMallocSpace::CreateFromMemMap(std::move(non_moving_space_mem_map),
"zygote / non moving space",
kDefaultStartingSize, initial_size, size, size,
/* can_move_objects= */ false);
//...
}
可见 Android 版 DirectByteBuffer 实际采用了简化实现:
并未使用 Cleaner(PhantomReference), 而是通过 MemoryRef:
持有 Runtime 分配的不可移动内存的 address,既不会隐式拷贝、也不用自己释放,还能高效访问;
维护可访问状态,读写操作都会检查;
字节序
Java 默认使用大端(IO 流、Buffer 等),ByteBuffer 可指定大小端:
ByteBuffer buf = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN);
buf.putInt(0x12345678); // 写入小端: [0x78, 0x56, 0x34, 0x12]
此外,还有之前的几篇涉及 JNI 内存管理的文章:
OC/C++ 混编场景内存管理
OC/C++ 混编很爽,但内存管理时,他们是两个独立世界:
“上帝的归上帝,凯撒的归凯撒。”
自动释放池
OC 主线程默认有 AutoReleasePool,子线程才需要手动创建;
但混编场景,C++ 函数最终被外部哪个线程调用无法预知,所以但凡内部调用 OC API 产生了 AutoRelease 对象 ,建议都包在
@autoreleasepool{}代码块。
#define NSString2CharP(nsstr) [nsstr cStringUsingEncoding:NSUTF8StringEncoding]
std::string osVersion() {
static const auto version = []() -> std::string {
@autoreleasepool {
NSString *nsVersion = UIDevice.currentDevice.systemVersion;
if (nsVersion == nullptr) [[unlikely]] {
return "Unknown";
}
const auto cstr = NSString2CharP(nsVersion);
if (cstr == nullptr) [[unlikely]] {
return "Unknown";
}
return {cstr};
}
}();
return version;
}
弱引用
风险
OC 的弱引用机制:
dealloc时,遍历 weak table,将所有指向该对象的__weak变量置为nil;前提是
__weak变量必须位于 Runtime 可追踪的内存(OC 对象实例变量、全局/栈上变量);
C++ 对象的析构与 OC 对象的
dealloc是两个独立生命周期系统:C++ 成员变量通常不在 OC Runtime 的弱引用表监控范围内;
编译器不会为 C++ 类中的
__weak自动生成objc_initWeak/objc_destroyWeak(实际上会退化为__unsafe_unretained);
实际上,Clang 社区曾讨论过是否应禁止在 C++ 成员中使用
__weak,但为了兼容旧代码,最终选择“允许但不实现语义”。
所以,如果 C++ 对象直接持有 OC __weak 成员,会导致严重内存问题:
__weak不会被自动置nil,成为悬垂指针;后续消息发送
EXC_BAD_ACCESS直接 crash。
class BadCppClass {
__weak id<FooProtocol> weakDelegate; // C++ 成员,OC Runtime 无法追踪
public:
BadCppClass(id<FooProtocol> mDelegate) : weakDelegate(mDelegate) {}
void doSomething() {
[weakDelegate process]; // 一旦外部释放,成为悬垂指针,可能 crash
}
};
解决方案
单独定义
NSObject子类持有__weak id;C++ 构造函数利用
id创建 OC 对象,析构函数显式释放 OC 对象(MRC 调用release,ARC 置为nil)。
@interface WeakRef : NSObject
@property (nonatomic, weak) id<FooProtocol> weakDelegate;
@end
@implementation WeakRef
@end
class GoodCppClass {
WeakRef* _ref;
public:
GoodCppClass(id<FooProtocol> mDelegate) {
_ref = [[WeakRef alloc] init];
_ref.weakDelegate = mDelegate; // Runtime 知道这个 weak 引用
}
~GoodCppClass() {
_ref = nil;
}
void doSomething() {
[_ref.weakDelegate process];
}
};
可通过
-Wunsupported-weak-receiver-type和-Wobjc-weak-cxx-member开启编译警告;
字节序
ObjC 为了兼容 C,默认也是小端。
Core Foundation 通过 CFByteOrder.h 提供大小端相关 API:
//获取当前机器大小端模式:
CFByteOrderGetCurrent();
//将 32 位的整型从大端转为本机的模式(若本机为大端,则原值不变)
uint32_t CFSwapInt32BigToHost(uint32_t arg);
//将 32 位的整型从本机的模式转为大端(若本机为大端,则原值不变)
uint32_t CFSwapInt32HostToBig(uint32_t arg);
//将 32 位的整型从小端转为本机的模式(若本机为小端,则原值不变)
uint32_t CFSwapInt32LittleToHost(uint32_t arg);
//将 32 位的整型从本机的模式转为小端(若本机为小端,则原值不变)
uint32_t CFSwapInt32HostToLittle(uint32_t arg);
此外,还有之前的一篇关于 OC/C++ 混编的文章有涉及内存管理相关问题: