C 中晦涩难懂的“restrict”关键字

c 中晦涩难懂的“restrict”关键字

介绍

除此之外,c++99 添加了 limit 关键字,作为程序员指定指针是指向作用域中给定对象的唯一指针的一种方式,从而给编译器一个“提示” ”,当通过该指针访问对象时,它可能会执行额外的优化。

问题

为了说明限制要解决的问题,请考虑如下函数:

void update_ptrs( int *p, int *q, int const *v ) {  *p += *v;  *q += *v;}

编译器将生成 x86-64 代码,例如:

mov eax, [rdx]  ; tmp = *v   // 1add [rdi], eax  ; *p += tmpmov eax, [rdx]  ; tmp = *v   // 3add [rsi], eax  ; *q += tmp

你可能想知道为什么它生成第 3 行,因为它看起来与第 1 行是多余的。问题是编译器不知道你没有做这样的事情:

int x = 1, v = 2;update_ptrs( &v, &x, &v );   // x = 5, v = 4

在 update_ptrs() 中,p 和 v 会别名 相同 int,因此编译器必须谨慎行事并假设 *v 的值可以在读取之间发生变化,因此需要额外的 mov 指令。

一般来说,c 中的指针会混淆优化,因为编译器无法知道两个指针是否彼此别名。 在性能关键的代码中,消除内存读取可能是一个巨大的胜利如果编译器可以安全地做到这一点。

解决方案

为了解决上述问题,c 中添加了 limit,允许您指定给定指针是 唯一 指向该指针作用域中的对象的指针,即同一作用域别名中没有其他指针它。

要使用限制,请将其插入声明中的 * 和指针名称之间。 重写为使用限制的 update_ptrs() 将是:

void update_ptrs_v2( int *restrict p, int *restrict q,                     int const *restrict v ) {  *p += *v;  *q += *v;}

(从右到左读取,例如 v 是指向常量 int 的受限指针;或使用 cdecl。)

通过添加限制,编译器现在可以生成如下代码:

mov eax, [rdx]  ; tmp = *vadd [rdi], eax  ; *p += tmpadd [rsi], eax  ; *q += tmp

现在,编译器能够删除附加 mov 指令的前第 3 行。

也许最知名的使用restrict的例子是标准库函数memcpy()。 这是复制内存块的最快方法如果源地址和目标地址重叠。当地址 重叠时,可以使用稍慢的 memmove() 函数。

陷阱

滥用限制会导致未定义的行为,例如,将 do 彼此别名的指针传递给 update_ptrs_v2() 或 memcpy()。 在某些情况下,编译器可以警告您,但并非在所有情况下,因此不要依赖编译器来捕获误用。

请注意,限制是针对给定范围的。 将一个受限制的指针分配给同一范围内的另一个会导致未定义的行为:

void f( int *restrict d, int *restrict s ) {  int *restrict p = s;    // undefined behavior

但是,您可以将受限制的指针分配给不受限制的指针:

void f( int *restrict d, int *restrict s ) {  int *p = s;             // ok

即使 p 不受限制,编译器仍然可以执行相同的优化。

也可以将内部作用域中的受限指针分配给外部作用域中的另一个受限制指针(但反之则不然):

void f( int *restrict d, int *restrict s ) {  {                       // inner scope    int *restrict p = s;  // ok    // ...    s = p;                // undefined behavior  }}

何时(以及何时不)使用限制

首先,您绝对应该分析您的代码(甚至可能查看生成的汇编代码),看看使用限制是否确实能够带来显着的性能改进,以证明冒潜在陷阱的风险是合理的。 诊断因滥用限制而导致的错误非常很难做到。

其次,如果限制的使用仅限于实现通过受限指针访问的内存由you分配的函数,那么它会更安全。 例如,给定:

void safer( unsigned n ) {  n += n % 2 != 0;  // make even by rounding up  int *const array = malloc( n * sizeof(unsigned) );  unsigned *restrict half_1st = array;  unsigned *restrict half_2nd = array + n/2;  // ...  free( array );}

代码可以安全地对数组的前半部分和后半部分进行操作,因为它们不重叠(假设您从未访问 half_1st[n/2] 或更多)。

第三,如果在函数的参数中使用restrict,那么它可能不太安全。 例如,将 safer() 与 update_ptrs_v2() 进行对比,其中 调用者 控制指针。 据所知,调用者错误并传递了别名的指针。

各种各样的

只有指向对象(或void)的指针可以用restrict限定:

restrict int x;       // error: can't restrict objectint restrict *p;      // error: pointer to restrict objectint (*restrict f)();  // error: pointer-to-function

可以对结构体成员使用restrict,例如:

struct node {   void *restrict data;   struct node *restrict left;   struct node *restrict right;};

表示 data 将是指向该数据的唯一指针,并且 left 和 right 永远不会指向同一个节点。 然而,对结构成员使用限制是非常不常见的。

最后,c++ 没有有限制。 为什么不呢? 答案很长,但 tl;dr 版本是:

它可能是 c++ 委员会不想从 c 导入的难以发现的错误的来源。c++ 越来越多地使用指针,例如这个,使得安全使用限制变得更加困难。

但是,许多编译器都有 __restrict__ 作为扩展。

结论

在有限的情况下,使用限制可以提高性能,但也存在一些重大缺陷。 如果您正在考虑使用限制,请先分析您的代码。

明智地使用。

以上就是C 中晦涩难懂的“restrict”关键字的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1459038.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月18日 11:08:19
下一篇 2025年12月9日 17:30:28

相关推荐

  • C++ 函数的黑暗面:智能指针的使用技巧

    C++ 函数的黑暗面:智能指针的使用技巧 在 C++ 中,内存管理是一个经常令人头疼的问题。智能指针可以帮助减轻这种痛苦,但它们也可能隐藏一些危险。本文将揭示智能指针的黑暗面,并提供实战案例来演示如何使用它们。 智能指针的简介 智能指针是一种 C++ 类,它封装了一个原始指针并自动管理其生命周期。这…

    2025年12月18日
    000
  • C++ 函数的黑暗面:查找竞争条件的有效方法

    在 c++++ 中查找竞争条件的有效方法包括:使用锁,例如互斥对象,限制对共享数据的访问;使用数据竞赛检测器,如 addresssanitizer 或 threadsanitizer,持续监视代码并报告潜在竞争条件。 C++ 函数的黑暗面:查找竞争条件的有效方法 竞争条件是指多个线程访问共享数据并导…

    2025年12月18日
    000
  • C++ 函数的黑暗面:掌握异常处理艺术

    在 c++++ 中,异常处理是处理非预期事件(如内存分配失败)的机制,它包括 try 块(包含可能引发异常的代码)、catch 块(捕获并处理匹配类型的异常)、throw 语句(显式引发异常)。为了进行有效的异常处理,请遵循最佳实践:只捕获所需的异常、始终处理异常、使用适当的异常类型、记录异常。这样…

    2025年12月18日
    000
  • C++ 函数的秘密武器:利用调试器进行无缝故障排除

    c++++ 中的调试器是故障排除的利器。它允许你:设置断点暂停程序执行。使用“下一步”按钮逐行执行代码。使用“进入”按钮进入函数。使用“检查”命令检查变量的值。 C++ 函数的秘密武器:利用调试器进行无缝故障排除 在 C++ 中开发时,调试器是你的秘密武器,它能让你在代码中无缝故障排除。有了调试器,…

    2025年12月18日
    000
  • C++ 函数的黑暗面:虚函数的幽灵

    摘要:虚函数可以导致性能问题,称为“虚函数的幽灵”,因为编译器会插入间接寻址层。为了避免这种问题,可以采用以下技术:仅在需要时使用虚函数。使用虚函数指针。尽可能使用非虚函数。 C++ 函数的黑暗面:虚函数的幽灵 简介 虚函数是 C++ 中强大的工具,它允许我们轻松实现多态性。然而,如果不加注意,它们…

    2025年12月18日
    000
  • C++ 函数的黑暗面:跨平台兼容性挑战

    跨平台 c++++ 函数存在兼容性挑战,原因是名称修饰会导致函数调用不一致。为了解决此问题,应使用头文件包含来确保所有编译器看到相同的函数声明,从而避免名称修饰差异。例如,在一个名为 my_functions.h 的头文件中声明 extern “c” int my_funct…

    2025年12月18日
    000
  • C++ 函数的黑暗之旅:破解复杂的调试挑战

    破解 c++++ 函数调试挑战:识别函数调用的奥秘,包括堆栈帧和返回地址。掌握栈溢出和栈下溢的调试技巧,使用调试器检查堆栈并优化堆栈占用。处理指针陷阱,包括避免悬垂指针和调试内存泄漏。通过实战案例学习调试复杂函数,检查栈溢出、内存泄漏和边界访问问题。 C++ 函数的黑暗之旅:破解复杂的调试挑战 在 …

    2025年12月18日
    000
  • C++ 函数的修复指南:一步步解决调试问题

    修复 c++++ 函数的逐步调试指南:确保最新代码,编译并运行,注意错误。启用编译器警告和优化选项。检查函数签名,包括参数类型、返回值类型和名称。使用调试器或逐段调试以检查变量值和逻辑错误。验证输入/输出值是否符合预期。处理异常,包括抛出和捕获,以提高健壮性。优化代码以提升性能,例如内联化和循环展开…

    2025年12月18日
    000
  • C++ 函数中的幽灵陷阱:如何追踪和捕获

    C++ 函数中的幽灵陷阱:如何追踪和捕获 在 C++ 函数中,隐式的内存分配和释放会带来微妙的错误,被称为”幽灵陷阱”。以下是如何检测和处理此陷阱: 检测幽灵陷阱 使用编译器标记(例如 -fsanitize=memory)以检测内存错误。在内存分配和释放代码周围添加断言,以验…

    2025年12月18日
    000
  • C++ 函数的探险之旅:揭示调试奥秘的秘密地图

    调试 c++++ 函数时,需要掌握调试技能和工具,包括调试器、断点和变量监视。常见的陷阱有未初始化变量、指针错误、无限循环和逻辑错误。通过实战案例展示如何使用调试器逐步执行函数以查找错误,例如调试计算斐波那契数的函数中的基本情况错误。 C++ 函数的探险之旅:揭示调试奥秘的秘密地图 函数在 C++ …

    2025年12月18日
    000
  • C++ 函数的黑暗面:内存泄露检测与修复

    c++++ 中内存泄露,在分配内存后但使用完毕却没有释放时发生。检测方法包括使用调试器、内存分配器或自定义工具。修复步骤包括确定泄露源、分析原因和释放未使用的内存,使用智能指针能帮助防止泄露。 C++ 函数的黑暗面:内存泄露检测与修复 内存泄露是 C++ 程序常见的缺陷,会导致程序非预期的内存耗尽。…

    2025年12月18日
    000
  • C++ 函数的黑暗面:调试技巧对照表

    调试 c++++ 函数时,可以使用以下技巧:设置断点以暂停执行并检查变量状态。单步调试以逐行执行代码。检查调用堆栈以了解函数调用路径。使用调试器方便地设置断点和单步调试。添加调试输出以输出中间变量和函数调用。确保传入参数有效。捕获并处理异常以获得错误信息。使用内存检查器检测内存错误。编写单元测试以自…

    2025年12月18日
    000
  • C++ 函数的黑暗面:揭开指针操作的神秘面纱

    c++++ 函数中,指针参数和返回值可能导致危险,因为程序员需要保证指针指向有效内存(避免悬垂指针)和处理指针所有权(避免内存泄漏)。最佳实践包括:检查指针有效性、使用 nullptr 表示空指针、使用智能指针管理所有权以及谨慎使用指针作为函数参数和返回值。 C++ 函数的黑暗面:揭开指针操作的神秘…

    2025年12月18日
    000
  • C++ 函数的黑暗面:理解多态性和虚方法

    多态性允许不同类型的对象表现出不同的行为。虚方法使用虚函数表在运行时解析具体实现,但可能导致开销、不可预测性和脆弱性。实践中,动态绑定可避免意外行为,例如测量对象执行时间时调用基类函数而不是派生类函数的情况。 C++ 函数的黑暗面:理解多态性和虚方法 多态性和虚方法是 C++ 中强大的概念,使代码更…

    2025年12月18日
    000
  • C++ 函数的黑暗面:类的成员函数的噩梦

    c++++ 类的成员函数隐藏着一些陷阱,包括隐式 this 指针的意外修改、常量函数不能直接修改对象状态,以及嵌套函数访问控制的复杂性。这些陷阱可能导致破坏对象引用、编译错误和不可预期的行为。 C++ 函数的黑暗面:类的成员函数的噩梦 在 C++ 开发中,类的成员函数看似简单,却暗藏着不少陷阱。本文…

    2025年12月18日
    000
  • C++ 函数的黑暗面:单元测试最佳实践

    单元测试 c++++ 函数的最佳实践:管理依赖项:使用依赖注入技术将依赖项作为测试方法的参数传入。处理引用和指针:创建指向模拟对象的指针或引用来测试传递指针或引用的函数。隔离副作用:使用 mock 对象和桩来控制依赖项的行为,避免副作用影响测试结果。 C++ 函数的黑暗面:单元测试最佳实践 在编写健…

    2025年12月18日
    000
  • C++ 函数的黑暗面:时间复杂度优化策略

    在 c++++ 中,函数的时间复杂度至关重要,因为它会影响应用程序的响应能力。通过了解时间复杂度,我们可以使用各种优化策略来提高函数的效率,包括:避免不必要的复制使用适当的数据结构优化算法内联函数缓存结果通过应用这些策略,我们可以大幅提高 c++ 函数的性能,尤其是在处理大型数据集时。 C++ 函数…

    2025年12月18日
    000
  • C++ 函数的导师:从新手到调试专家的进阶之路

    函数是 c++++ 中可重用的代码模块,包括函数头和函数体。调用函数使用函数名和参数。调试函数包括设置断点、使用调试器、检查错误消息和添加日志记录。实战案例演示了如何使用和调试函数。高级特性包括重载、默认参数和函数指针。 C++ 函数的导师:从新手到调试专家的进阶之路 简介 函数是 C++ 中的一个…

    2025年12月18日
    000
  • C++ 函数的黑暗面:模板类的陷阱

    c++++ 模板类的陷阱包括:编译时类型不匹配错误,确保参数类型兼容。运行时错误,如整数溢出,考虑类型约束并添加显式转换或异常处理。可读性和可维护性,保持模板类简洁并使用清晰命名。依赖关系地狱,使用前向声明和类型别名管理依赖关系。 C++ 函数的黑暗面:模板类的陷阱 简介 C++ 模板类为生成可重复…

    2025年12月18日
    000
  • C++ 函数的黑暗面:引用和指针的区别与联系

    引用和指针在 c++++ 中的区别:引用必须绑定到一个变量并不能重新绑定,而指针可以重新分配。引用共享与被绑定的变量相同的内存地址,而指针存储被指向变量的内存地址。引用无需解引用就可以访问数据,而指针需要使用解引用运算符。引用必须始终指向有效的变量,而指针可以为空(nullptr)。 C++ 函数的…

    2025年12月18日
    000

发表回复

登录后才能评论
关注微信