未对齐的指针与 std::shared_ptr<NSDate> 取消引用一起使用

Misaligned pointer use with std::shared_ptr<NSDate> dereference

我在遗留代码库中工作,其中有大量 Objective-C++ 使用手册 retain/release 编写。内存使用大量 C++ std::shared_ptr<NSMyCoolObjectiveCPointer> 进行管理,并在构建时传入一个合适的删除器,该删除器在包含的对象上调用 release。这似乎很好用;然而,当启用 UBSan 时,它会抱怨指针未对齐,通常是在取消引用 shared_ptrs 来做一些工作时。

我已经搜索了线索and/or解决方案,但是很难找到Objective-C对象指针来龙去脉的技术讨论,更难找到任何关于Objective-C++的讨论,所以我来了。

这是一个完整的 Objective-C++ 程序,它演示了我的问题。当我 运行 在我的 Macbook 上使用 UBSan 时,我在 shared_ptr::operator* 中遇到指针未对齐的问题:

#import <Foundation/Foundation.h>
#import <memory>

class DateImpl {
public:
    DateImpl(NSDate* date) : _date{[date retain], [](NSDate* date) { [date release]; }} {}

    NSString* description() const { return [&*_date description]; }

private:
    std::shared_ptr<NSDate> _date;
};

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        DateImpl date{[NSDate distantPast]};
        NSLog(@"%@", date.description());
        return 0;
    }
}

我在 DateImpl::description 的电话中得到了这个:

runtime error: reference binding to misaligned address 0xe2b7fda734fc266f for type 'std::__1::shared_ptr<NSDate>::element_type' (aka 'NSDate'), which requires 8 byte alignment
0xe2b7fda734fc266f: note: pointer points here
<memory cannot be printed>

怀疑 &* 到 "cast" 和 shared_ptr<NSDate>NSDate* 的用法有问题.我想我可以通过在 shared_ptr 上使用 .get() 来解决这个问题,但我真的很好奇发生了什么。感谢您的任何反馈或提示!

这里有一些转移注意力的东西:shared_ptr、手册 retain/release 等。但我最终发现即使是这个非常简单的代码(启用了 ARC)也会导致 ubsan 命中:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSDate& d = *[NSDate distantPast];
        NSLog(@"%@", &d);
    }
    return 0;
}

这似乎只是 [NSDate distantPast] 的问题(顺便说一句,[NSDate distantFuture],但不是 [NSDate date])。我得出结论,这些必须是在 Foundation 深处某处分配 sketchily/misaligned-ly 的单例对象,当您取消引用它们时,会导致指针读取未对齐。

(请注意,当代码只是 NSLog(@"%@", &*[NSDate distantPast]) 时,它 不会 发生。我认为这是因为编译器只是在原始指针上折叠 &*进入无操作。它不适用于原始问题中的 shared_ptr 情况,因为 shared_ptr 重载 operator*。鉴于此,我相信没有简单的方法可以在纯 Objective-C,因为您不能将 & 操作与 * 操作分开,就像在涉及 C++ 引用时一样 [通过将 * 的临时结果存储在一个 NSDate&].)

您不应该使用 "bare" NSDate 类型。 Objective-C 对象应始终与指向对象的指针类型一起使用(例如 NSDate *),并且您永远不应该获得 "type behind the pointer".

特别是,在 64 位平台上,Objective-C 对象指针有时可能不是有效指针,而是 "tagged pointers" 将对象的 "value" 存储在某些位中的指针,而不是作为实际分配的对象。您必须始终让 Objective-C 运行时机制处理 Objective-C 对象指针。将其作为常规 C/C++ 指针取消引用会导致未定义的行为。