
-
安全地混合使用 C、C ++ 和 Swift
了解如何混合使用 C、C++ 和 Swift,同时提高 App 的安全性。我们将介绍如何在 Swift 代码中找到不安全的 C 和 C++ API 调用位置、如何更安全地调用这些 API,以及如何使 App 中的现有 C 和 C++ 代码默认处于更安全的状态。
章节
- 0:00 - 简介
- 2:39 - 在 Swift 中查找不安全的调用
- 4:55 - 安全调用 C/C++
- 7:25 - 接受指针的函数
- 17:13 - 返回指针的函数
- 20:16 - 导入自定类型
- 26:57 - 增强 C/C++ 的安全性
- 30:48 - 总结
资源
相关视频
WWDC25
-
搜索此视频…
大家好 我叫 Yeoul 我是 Apple Secure Language Extension 团队的经理 开发 App 时必须将安全性放在首位 这意味着要保护用户的隐私信息 免受潜在攻击者的侵害 这些恶意攻击者经常 利用 C/C++ 这类 不安全语言编写的代码漏洞进行攻击 值得庆幸的是 如果你的 App 已经采用 Swift 语言 那么默认情况下就是安全的 这无疑是个好消息
但即便所有新代码都用 Swift 编写 App 中可能仍存在旧代码库 遗留的 C/C++ 代码 或是依赖某些外部函数库 当你混合使用这些语言时 务必确保 Swift 的安全特性 不会被破坏 从宏观层面看 问题的核心在于 那些接收或返回 原始指针的 C/C++ 函数 极难安全调用 不当的调用方式 可能导致缓冲区溢出、 释放后使用等严重影响 安全性和稳定性的漏洞
因此 从 C/C++ 导入到 Swift 的指针都会被标记为“不安全”类型 例如 C 语言中的整型指针会被导入 为 UnsafeMutablePointer 类型 Swift 特意在类型名称中加入 “不安全”字样 就是为了提醒你 调用这类函数时无法获得 Swift 常规的安全保障 但是 我知道有些函数 可以从 Swift 安全地调用 但直到现在 C/C++ 仍缺乏向 Swift 传递安全调用方式的机制 而这就是本次讲座要解决的核心问题
首先 我将介绍一项名为 “严格内存安全”的新功能 它能帮助识别 Swift 中的 不安全调用
其次 我会讲解如何通过注解 C/C++ 函数 来补全关键信息 使 Swift 能安全调用它们 接着还将演示如何为自定义 C++ 类型添加注解以实现安全导入 最后 虽然基于 C 的语言永远 无法达到 Swift 的安全级别 但我将分享一些能提升 C/C++ 代码安全性的工具
Swift 6.2 有个很棒的新功能 用于 检测对不安全 C/C++ 函数的调用 我会用自己开发的一款 App 来演示 这款让用户分享爱宠萌照的 App 可以访问用户高度敏感信息 比如狗狗的中间名 因此必须确保安全性 虽然主体用 Swift 编写 但在调用 C/C++ 代码 实现自定义图像滤镜时 我需要找出 App 内所有 不安全的 C/C++ 调用 并进行安全改造 难点在于 这些调用 有时很难被及时发现 尽管 Swift 默认是安全的 但尤其在与 C/C++ 交互时 仍会涉及不安全结构 例如 percentImageData 方法在底层 创建了一个 UnsafeMutablePointer 从类型名称就能看出这是不安全的 但实际阅读代码时 这种风险很容易被忽视 现在 Swift 6.2 新增的 严格内存安全 编译模式可以帮助我们定位 所有不安全函数调用 启用“严格内存安全”后 编译器会提醒我所有不安全代码 并附上解释推理的节点
虽然严格内存安全模式默认关闭 但由于我的 App 涉及敏感数据 我决定立即启用它 让我们在 Xcode 中实际操作一下
在项目构建设置中 将“严格内存安全”切换为“yes”
重新编译后 编译器给出了多个警告 帮助我识别不安全结构
我现在看到一些新的警告 其中大部分不安全代码 都与 C/C++ 指针相关
接下来我会演示如何安全地 从 Swift 调用这些指针
但首先我们来谈谈为何 C/C++ 指针难以安全使用
指针确实是个强大而实用的工具 它能让我们高效地直接访问内存 而无需复制数据 但它的安全性却极难保障 根本原因在于 C/C++ 本身缺乏防止程序员犯错的设计 例如 内存被释放后 仍可能被错误使用 或者缓冲区边界外的访问 也无法被阻止
好消息是 Swift 6.2 引入了一种名为 Span 的新型安全指针类型 它在保留指针优势的同时 能自动防范上述错误 Span 的行为类似于指针 但内置了安全机制 Swift 会确保你无法 用它执行危险操作 如需修改内存 还有对应的 MutableSpan 类型 想深入了解 Span 请参考“优化 Swift 代码的 内存使用和性能”讲座 如果 Swift 能够将 C/C++ 指针 直接导入为 Span 而非不安全指针 那将非常理想
但问题在于 编译器缺少两项关键信息 来实现安全转换 没有 C++ 提供的指针边界信息 Swift 就无法防范越界访问 缺少指针生命周期的 C++ 标注 Swift 就不能阻止释放后使用的错误 核心思路是 只要开发者 补全这些缺失的信息 Swift 就能将不安全指针 视为 Span 来安全处理
在 Swift 6.2 中 我可以通过 为 C/C++ 代码添加注解 来向编译器提供这些信息 这些注解不会改变原有代码行为 只是对它进行了显式声明 这样一来 Swift 就能安全地调用 涉及指针的 C/C++ 代码 让我们来谈谈如何为 接收和返回指针的函数添加注解 回到之前的 App 案例
第一个警告出现在 调用 invertImage 函数时 这个函数接收了原始指针
我将演示如何安全地调用这类 以指针为参数的函数
正如之前所说 原始指针缺乏边界信息 因此无法验证它们的访问是否越界 若不谨慎使用 极易引发内存越界错误 例如 当 InvertImage 函数 从 Swift 调用时 它接收一个指向图像的 原始指针 imagePointer 而图像尺寸则通过单独参数传递
但由于 imagePointer 仅是原始指针 若我误传过大的尺寸参数 代码也无法阻止 如果这样 函数就会读写 超出缓冲区边界的内存 这正是 Span 类型要解决的问题之一 设想如果 invertImage 能被 导入为接收 Span 的 Swift 函数 我就能直接传递 Span 对象 而非分别传递原始指针和尺寸 这将自动规避传错尺寸这类错误 因为 Span 始终携带指向内存的 准确边界信息 在底层 编译器会自动解包 Span 提取正确的指针和尺寸 传递给 C 函数 从而彻底消除人为失误的可能
编译器可以做到这一点 但它缺少原始指针 与尺寸参数之间的关联 invertImage 函数默认指针 指向一个包含 imageSize 个元素的缓冲区 但这只是隐式假设 我需要明确表达这种关系 让人和编译器都能理解 这可以通过 counted_by 注解来实现 这个注解会告知编译器 指针所指向内存的元素数量 此外 由于缺少生命周期信息 指针还需要添加 noescape 注解 这个可以先忽略 稍后再详细讨论 提供这些额外信息后 现在我就能通过直接传递 Span 来安全地从 Swift 调用这个函数 编译器会自动处理后续所有操作 从而完全杜绝错误发生的可能 现在回到 invertImage 函数
我将在 invertImage 函数上添加 counted_By 和 noescape 注解
然后跳转到装饰部分添加相同注解
然后返回 Swift 调用端
改为直接传递 imageData spent 来调用来自 Swift 的函数
现在 由于不再涉及不安全指针 警告随即消失
下一个警告出现在 applyGrayScale 函数 提示使用了不安全的 C++ 类型
让我们看看这个函数的 C++ 定义 顾名思义 applyGrayScale 是个对输入图像 应用灰度效果的函数 这个函数接收一个图像视图 可以看到它是 C++ Span 类型 之前我们讨论的都是 Swift 中的 Span 但 C++ 也有自己的 Span 实现 之所以会触发警告 是因为 Swift 将 C++ 的 Span 视为不安全类型 尽管两者试图解决的是相同的问题 与 Swift 的 Span 类似 C++ 的 Span 是用于访问他人 所有的连续内存的标准类型 包含指向内存的指针及它的大小信息 由于知晓自身尺寸 C++ 的 Span 也能安全检测越界访问 这点与 Swift 的 Span 一致 但与 Swift Span 不同的是 C++ Span 缺乏生命周期信息 因此无法防止访问已释放的内存 这可能导致“释放后使用”漏洞
举例说明 假设 applyGrayScale 函数接收 一个名为 imageView 的 C++ Span 它指向由 Swift 创建的数组
applyGrayScale 内部可能将指针 存储在全局变量 如 cachedView 中 供其他 C++ 代码后续使用
但问题来了 当函数返回后 Swift 可能认为数组 不再被引用而将它释放 此时 C++ 代码持有一个 指向无效内存的指针 这就是所谓的悬垂指针 而访问这种指针正是典型的 “释放后使用”漏洞
相比之下 Swift 的 Span 则是安全的 它绝不允许比指向的内存存活更久 当函数接收 Swift 的 Span 时 只能在函数内部使用它 禁止将它存储到 cachedView 等变量中延后使用 这种在函数结束后仍保留指针的行为 被称为“逃逸” 每当 Span 试图逃逸作用域时 编译器就会报错
这样一来 Swift 的 Span 就不可能出现悬垂指针 从设计层面规避了释放后使用漏洞
而 C++ 的 Span 则 无法提供同等保障 要安全使用它 我必须手动审查函数逻辑 确保不会保留 imageView 参数以避免悬垂指针
当确认参数 不会逃逸 C++ 函数后 我需要通过函数定义 显式记录这一信息 让编译器和开发者都能 理解这个函数的行为特性 这正是 noescape 注解的作用所在 noescape 注解同样适用于 原始指针和引用
从 Swift 6.2 开始 带有 noescape 注解的 C++ Span 参数可被视作 Swift Span 这意味着我现在可以直接将 Swift Span 传递给 applyGrayscale 这种操作不仅极其便捷 还彻底 消除了那些不安全的样板代码 能让 C++ 函数调用变得 如此安全简单 着实令人惊叹
我将跳转到应用灰度的定义处
为参数 imageView 添加 noescape 注解
同时在装饰处也添加相同注解
然后回到 Swift 调用端
移除对未保存可变缓冲指针 的临时访问 直接传递从 imageData 获取的 Swift Span
现由于安全调用了 applyGrayscale 相关警告随即消失
下一个警告出现在 scanImageRow 函数的使用处 这个函数不仅接收 C++ Span 还返回另一个 C++ Span
那么如何安全调用这种返回指针 如 C++ Span 的函数呢?
返回 C++ Span 存在风险 因为它无法追踪 所指向的内存是否仍然有效 例如 scanImageRow 接收以 C++ Span 形式传入的 imageView 参数 返回指向 imageData 某行的 另一个 C++ Span 当函数返回后 数据将被释放 但返回的 C++ Span 仍指向这个内存区域 形成悬垂指针 此时访问它就会导致释放后使用错误 如果返回值能被当作 Swift Span 而非 C++ Span 这个问题就不会发生 因为编译器在 允许返回 Swift Span 前 必须确认目标内存仍然有效 且可安全访问 那么何时可以安全使用 返回的 Span 呢? 它实际上指向与 imageView 参数相同的内存区域 这意味着它的生命周期 与 imageView 严格绑定
这种关系被称为“生命周期绑定”
这正是 Swift 将返回的 C++ Span 安全导入为 Swift Span 所缺失的信息 通过添加 lifetimebound 注解 我可以明确表达这种关系 使编译器能够实施安全验证
我将跳转到 scanImageRow 函数定义处
为 scanImageRow 的 imageView 参数添加 lifetimebound 注解
并在函数声明处添加相同注解
添加 lifetimebound 注解后 这个函数就能直接接收 Swift Span 并返回另一个 Swift Span 回到 Swift 调用端
移除不安全指针访问操作 直接传递 Swift 的 Span 对象
函数现在返回的已是 安全的 Swift Span 类型 重新编译后 相关警告完全消失
现在已成功解决调用 C/C++ 代码时的所有安全隐患 所有警告均已消除 截至目前 我讨论了如何将 C/C++ 指针当作 Swift Span 来处理 但 C++ 中还存在其他一些 符合语言习惯的类型 只需通过适当注解就能 直接安全导入 Swift 使用 主要包括自定义视图 和引用计数两大类 首先来看如何安全导入 C++ 的自定义视图类型 视图类型是指那些 包含指针或引用的 指向非自有内存的结构体 这意味着 Swift Span 也是一种视图类型
因此 让我们深入剖析 Swift Span 的安全机制本质 实际上 Swift 在底层将 Span 标记为一种特殊类型 即不可逃逸类型 这类类型通常用于 实现内存视图模式 即无需拷贝数据即可 访问其他类型的内存 与 Span 一样 Swift 会确保所有不可逃逸类型 都不会逃逸出当前上下文 从而保证生命周期 不会超过所指向的内存 从根本上防范释放后使用漏洞 C++ 的视图类型也可被安全地 导入为 Swift 的不可逃逸类型 只需添加一个注解即可实现
例如 我的 App 中有一个自定义 C++ 结构体 ImageView 它存储了图像的宽度、高度 以及指向像素数据的指针 imageView 并不拥有这些像素数据 实际数据归属于另一个对象 这个对象负责在 不再需要内存时释放内存
这意味着如果 允许 imageView 逃逸 就可能出现视图在底层内存释放后 仍被访问的危险情况
因此我想确保这个类型绝不逃逸 为此 我可以添加 SWIFT_NONESCAPABLE 注解 注解会指示编译器将 C++ 类型作为不可逃逸类型导入 一个很好的经验法则是 只要结构体包含指向 非自有内存的视图或指针 就应该使用这个注解
除了视图类型 C++ 和其他语言中 还存在另一种常见模式 这类类型通过引用计数 来管理内存所有权并跟踪引用 Swift 提供了一项通过注解 安全导入这些类型的功能 例如 我在 C++ 中定义的图像缓冲区 结构体拥有底层 imageData 结构体被释放时 imageData 也会随之释放 为了让 Swift 将图像缓冲区 作为引用计数类型导入 从而实现编译器的 自动生命周期管理 我将使用 SWIFT_SHARED_REFERENCE 注解 这个注解会告知 Swift 编译器 应该调用哪些函数 来增加和减少引用计数 现在 Swift 已将图像缓冲区 识别为引用计数类型
但要让编译器安全地 返回图像缓冲区 还需要更多信息
当 C++ 函数返回 图像缓冲区时 可能存在两种情况 首先如果函数返回的是 新创建的图像 调用方需在使用完毕后 负责释放图像 我会用 SWIFT_RETURNS_RETAINED 注解标记这个方法 告知 Swift 编译器 在调用方不再使用时 释放这个图像 其次 如果函数返回的是 对已有图像的引用 则调用方如需保留图像 须自行持有 此时我会将方法标记为 SWIFT_RETURNS_UNRETAINED 提示 Swift 编译器在 需要保留图像时 须自行持有 添加这些注解可以明确所有权语义 使 Swift 能够安全地管理内存
这些注解通过补充缺失的信息 让 Swift 可以安全地使用 C/C++ 函数和相关类型 这些注解本身不会改变代码行为 它们只是将代码中 隐含的假设显式化
下面我们总结一下 已介绍的注解及用法
当函数参数或返回值 是指针或数组类型 且指向多个元素的内存时 添加 counted_by 注解指明元素数量 当参数引用他人拥有的内存 且不会逃逸函数作用域时 使用 noescape 注解
当返回值内存生命周期 依赖于某个参数时 添加 lifetimebound 注解 通过添加这些信息 你可以让 Swift 将指针安全地 导入为 Swift Span 类型 这种类型在调用时 无需额外处理即可直接使用
你还可以添加注解来帮助 Swift 安全地管理自定义 C++ 类型 如果 C++ 类型存储了一个 它并不拥有的视图、指针 或引用 请使用 SWIFT_NONESCAPABLE 这会告诉编译器将这个类型 作为不可逃逸类型导入
如果你的类型是引用计数的 则应 使用 SWIFT_SHARED_REFERENCE 这样编译器就能自动管理内存了
没错 内容确实不少 如果你想暂停视频 去吃点零食或喝口水 现在是个好时机 只要答应我会回来就行 因为接下来我将展示一些 真正令人兴奋的新工具 它们能大幅提升 C/C++ 代码的安全性
好了 换个话题 谈谈如何切实提升 C/C++ 代码的安全性 在我的 App 中 虽然已通过添加注解确保 Swift 能安全调用 C/C++ 代码 但纯 C/C++ 代码本身 仍然存在安全隐患 稍有不慎就可能引发安全漏洞 最理想的情况是用 Swift 重写代码以获得完全的安全性 但有时这并不现实 虽然无法让 C/C++ 像 Swift 那样安全 但这有一些工具可以在 C/C++ 中提供部分安全保障 首先介绍我们开发的增强 C++ 边界安全性的工具
你们有些人会疑惑 既然 前面讲 Span 已存储边界信息 为何 C++ 仍不具备边界安全性? 问题在于 像这样的 Span 数组下标操作 在 C++ 中默认不会进行边界检查 其他像向量这样的标准容器 同样存在这个问题 Xcode 有一项名为 “C++ 标准库强化”的功能 它对标准 C++ 视图和容器的 数组下标操作 强制实施边界检查 同时为标准库添加了 其他安全检查机制
但启用“C++ 标准库强化”后 仍存在另一个问题
原始指针依然可以使用 而由于缺乏边界信息 这些指针根本无法进行边界检查 因此 使用 C++ 的最佳方式 就是避免原始指针 改用像 C++ Span 这样的标准类型
为此 Xcode 允许你开启 针对 C++ 中不安全指针的 报错功能 这样 你就可以检查代码 并按需将原始指针替换为 C++ Span 或标准容器
不过需要注意 这些错误 仅针对边界安全性 而非生命周期安全性
若要在 C++ 项目中实现边界安全 只需在构建设置中将 C++ 中的 “强制边界安全的缓冲区使用” 设为“是” 这将启用“C++ 标准库强化” 和“不安全缓冲使用报错”两项功能
那么 C 语言呢? 与 C++ 不同 C 语言缺乏 类似 Span 的标准类型 来让指针携带边界信息 为此我们开发了全新的语言扩展 来确保边界安全 现在你可以在 Xcode 中直接使用 启用这个语言扩展后 编译器会告诉你 C 代码中所有 缺失边界信息的位置 你只需补充相应的边界注解即可 在这个例子中 你可以为缓冲区 添加 counted_by 注解 就是之前用于 Swift 与 C 安全交互的同款注解
编译器就会自动插入运行时边界检查 从而安全拦截越界内存访问
我可以在 Xcode 项目设置中 为所有 C 文件启用边界安全扩展 如需了解更多详情 请查看 llvn.org 网站上的 边界安全文档
在本次讲座中 我介绍了 如何确保 Swift 的安全性 并安全地调用 C/C++ 代码 虽然无法让 C/C++ 像 Swift 那样安全 但它们可以变得更安全
现在来捋捋在混合使用 C、C++ 和 Swift 时 提高安全性的一些建议
在 Swift 中启用“严格内存安全” 这会在你使用不安全结构时发出警告 并帮助你发现 C/C++ API 的任何不安全用法 通过添加标注 确保 Swift 能 安全地与不安全的 C/C++ API 交互
默认提高 C/C++ 的安全性 你可以通过启用 C/C++ 的 新边界安全功能来实现这一点
我们正在与开源社区合作 让 C、C++ 和 Swift 能够 无缝且安全地协同工作 因此 你的反馈和参与 对我们至关重要
请尝试这些功能 并告诉我们你的想法 感谢观看
-
-
3:19 - Unsafety can be subtle
// Swift var imageData = [UInt8](repeating: 0, count: imageDataSize) filterImage(&imageData, imageData.count)
-
4:01 - Strict memory safety
// Swift var imageData = [UInt8](repeating: 0, count: imageDataSize) filterImage(&imageData, imageData.count) //warning: Expression uses unsafe constructs but is not marked with 'unsafe'
-
8:00 - Raw pointers don't prevent out-of-bounds errors
// C/C++ void invertImage(uint8_t *imagePtr, size_t imageSize);
-
8:21 - Raw pointers don't prevent out-of-bounds errors
// Swift var imageData = [UInt8](repeating: 0, count: imageSize) invertImage(&imageData, imageSize)
-
8:30 - Raw pointers don't prevent out-of-bounds errors
// Swift var imageData = [UInt8](repeating: 0, count: imageSize) invertImage(&imageData, 1000000000000)
-
8:48 - Solution for out-of-bounds error
// Swift func invertImage(_ imagePtr : inout MutableSpan<UInt8>)
-
8:54 - Solution for out-of-bounds error
// Swift var imageDataSpan = imageData.mutableSpan invertImage(&imageDataSpan)
-
9:58 - Express bounds information using __counted_by
// C/C++ void invertImage(uint8_t *__counted_by(imageSize) imagePtr __noescape, size_t imageSize);
-
12:10 - Unsafe function declaration taking a C++ span
// C++ using CxxSpanOfByte = std::span<uint8_t>; void applyGrayscale(CxxSpanOfByte imageView);
-
13:21 - Unsafe C++ function caching a C++ span
// C++ CxxSpanOfByte cachedView; void applyGrayscale(CxxSpanOfByte imageView) { cachedView = imageView; // Apply effect on image ... }
-
14:08 - Swift Span prevents escaping scope
// Swift var cachedView: MutableSpan<UInt8>? func applyGrayscale(_ imageView: inout MutableSpan<UInt8>) { cachedView = imageView // error: lifetime dependent value escapes its scope // Apply effect on image ... }
-
15:18 - Express lifetime information using __noescape
// C++ CxxSpanOfByte cachedView; void applyGrayscale(CxxSpanOfByte imageView __noescape) { // Apply effect on image ... }
-
15:56 - Safely use a C++ Span as a Swift Span
// Swift var imageDataSpan = &imageData.mutableSpan applyGrayscale(&imageDataSpan)
-
17:21 - Returned C++ Span is unsafe
// C++ CxxSpanOfByte scanImageRow(CxxSpanOfByte imageView, size_t width, size_t rowIndex);
-
18:06 - Swift Spans prevent use-after-free by design
// Swift func scanImageRow(_ imageView : inout MutableSpan<UInt8>, _ width : Int, _ rowIndex : Int) -> MutableSpan<UInt8> // error: a function with a ~Escapable result requires '@lifetime(...)'
-
18:47 - Express lifetime dependency with __lifetimebound
// C++ CxxSpanOfByte scanImageRow(CxxSpanOfByte imageView __lifetimebound, size_t width, size_t rowIndex);
-
18:50 - Safely return a C++ Span as a Swift Span
// Swift var imageDataSpan = imageData.mutableSpan var rowView = scanImageRow(&imageDataSpan, width, y)
-
22:29 - Import a C++ view type as SWIFT_NONESCAPABLE
// C++ struct ImageView { std::span<uint8_t> pixelBytes; int width; int height; } SWIFT_NONESCAPABLE;
-
23:31 - Import a C++ reference-counted type
// C++ struct ImageBuffer { std::vector<uint8_t> data; int width; int height; std::atomic<unsigned> refCount; } SWIFT_SHARED_REFERENCE(retain_image_buffer, release_image_buffer); void retain_image_buffer(ImageBuffer *_Nonnull buf); void release_image_buffer(ImageBuffer *_Nonnull buf);
-
23:57 - Safely return a reference-counted type
// C++ ImageBuffer *_Nonnull createImage() SWIFT_RETURNS_RETAINED; ImageBuffer *_Nonnull getCachedImage() SWIFT_RETURNS_UNRETAINED;
-
27:51 - C++ standard library hardening
// C++ void fill_array_with_indices(std::span<uint8_t> buffer) { for (size_t i = 0; i < buffer.size(); ++i) { buffer[i] = i; } }
-
28:59 - C++ unsafe buffer usage errors
// C++ void fill_array_with_indices(uint8_t *buffer, size_t count) { for (size_t i = 0; i < count; ++i) { buffer[i] = i; // error: unsafe buffer access } }
-
30:11 - Bounds safety extension for C
// C void fill_array_with_indices(uint8_t *__counted_by(count) buf, size_t count) { for (size_t i = 0; i < count; ++i) { buf[i] = i; } }
-
-
- 0:00 - 简介
了解 App 安全性,尤其是将 Swift (一种默认安全的语言) 与 C 和 C++ 混合使用时,后者可能很容易受到攻击。Swift 引入了一些新功能,以便在混合使用这些语言时增强安全性:严格的内存安全、C/C++ 函数和类型的注释,以及使 C/C++ 代码更安全的工具。
- 2:39 - 在 Swift 中查找不安全的调用
这里的示例演示了一个用 Swift 编写的 App,它为实现图像滤镜而调用了 C 和 C++ 代码,这可能会带来安全风险。Swift 6.2 全新的“严格内存安全”编译器模式用于识别和标记不安全代码 (主要涉及 C 和 C++ 指针),从而增强 App 的安全性。
- 4:55 - 安全调用 C/C++
Swift 6.2 引入了一种名为“Span”的新型安全指针类型。通过使用边界和生命周期信息来注释 C/C++ 代码,Swift 可以将不安全的指针转换为“Spans”,从而在不修改原始 C/C++ 代码的情况下从 Swift 进行安全函数调用。要进一步了解 Span,请观看“优化 Swift 代码的内存使用和性能”。
- 7:25 - 接受指针的函数
当函数采用原始指针时,由于没有边界信息,因此无法防止函数越界读取或写入。为了使编译器能够理解原始指针与其相应大小之间的关系,“counted_by”和“noescape”注释是必要的。当指针带有这些注释时,就可以使用 Span 从 Swift 调用这个函数。 这个示例还讨论了 Swift 和 C++“Span”类型之间的区别。虽然两者都旨在安全访问连续内存,但 C++“Span”缺乏生命周期信息,这可能会导致“释放后使用”错误。为了解决这个问题,我们使用“noescape”注释来指示参数 (无论是 C++“Span”还是原始指针) 不应在函数作用域之外存储和使用。 通过应用这些注释并使用“Span”,这个示例消除了不安全的指针使用,降低了内存相关错误的风险,并使代码更易于维护和可读,同时实现了 Swift 和 C/C++ 函数之间的无缝交互。
- 17:13 - 返回指针的函数
调用某个返回指针或 C++ Span 的 C/C++ 函数可能存在风险,因为它不会跟踪它指向的内存是否仍然有效。为解决这个问题,我们使用了“lifetimebound”注释,它使编译器能够强制安全使用,从而消除“释放后使用”错误。
- 20:16 - 导入自定类型
在 Swift 中,某些 C++ 惯用类型可以直接导入,并安全地与注释一起使用。自定视图类型是一种结构体,它们所包含的指针或引用指向它们不拥有的内存,可以使用“SWIFT_NONESCAPABLE”注释将这种结构体作为不可逃逸的类型导入。这可确保它们的生命周期不会超过指向的内存,从而防止“释放后使用”错误。 引用计数类型拥有它们引用的内存并通过计数跟踪引用,可以使用“SWIFT_SHARED_REFERENCE”注释导入。这使得 Swift 能够自动管理它们的生命周期。 此外,函数返回值可以注释为“SWIFT_RETURNS_RETAINED”或“SWIFT_RETURNS_UNRETAINED”,以指定调用方是否负责释放或保留返回的引用计数对象,从而明确所有权并使 Swift 能够安全地管理内存。
- 26:57 - 增强 C/C++ 的安全性
你可以使用一些工具来增强 C 和 C++ 中的边界安全性。 Xcode 的“C++ 标准库强化”支持对标准容器和视图进行边界检查,并且可以针对 C++ 中不安全的指针使用报错。为了边界安全,请在项目的构建设置中将“在 C++ 中强制边界安全的缓冲区使用”设置为“是”。 对于 C,我们新增了语言扩展,可以保证边界安全,这种语言扩展可以在 Xcode 中使用,它会要求你提供注释来表示边界信息,还会插入运行时边界检查。你可以在 Xcode 项目的设置中为所有 C 文件启用这个语言扩展。
- 30:48 - 总结
利用这个讲座中的信息,你可以安全地混合使用 Swift、C 和 C++ 代码。关键步骤包括在 Swift 中启用严格内存安全功能、对不安全的 C/C++ API 进行注释,以及利用 C/C++ 中新的边界安全功能。希望大家积极地反馈和参与,帮助我们提高互操作性。