C++中的restrict关键字是啥意思?

Posted

技术标签:

【中文标题】C++中的restrict关键字是啥意思?【英文标题】:What does the restrict keyword mean in C++?C++中的restrict关键字是什么意思? 【发布时间】:2010-10-21 01:10:51 【问题描述】:

我一直不确定,C++ 中的 restrict 关键字是什么意思?

这是否意味着给函数的两个或多个指针不重叠? 还有什么意思?

【问题讨论】:

restrict 是一个 c99 关键字。是的,Rpbert S. Barnes,我知道大多数编译器都支持__restrict__。您会注意到,根据定义,任何带有双下划线的内容都是特定于实现的,因此 不是 C++,而是特定于编译器的版本。 什么?仅仅因为它是特定于实现的,并不意味着它不是 C++; C++ 允许明确地实现特定的东西,并且不允许它或呈现它不是 C++。 @Alice KitsuneYMG 表示它不是 ISO C++ 的一部分,而是被视为 C++ 扩展。允许编译器创建者制作和分发他们自己的扩展,这些扩展与 ISO C++ 共存,并作为 C++ 的通常较少或不可移植的非官方补充的一部分。示例是 MS 的旧托管 C++,以及他们最近的 C++/CLI。其他示例包括一些编译器提供的预处理器指令和宏,例如常见的#warning 指令或函数签名宏(GCC 上的__PRETTY_FUNCTION__,MSVC 上的__FUNCSIG__ 等)。 @Alice 据我所知,C++11 并不要求完全支持所有 C99,C++14 或我所知道的 C++17 也没有。 restrict 不被视为 C++ 关键字(请参阅 en.cppreference.com/w/cpp/keyword ),事实上,在 C++11 标准中唯一提到 restrict (请参阅 open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf ,FDIS 的副本,稍作编辑更改, §17.2 [library.c], PDF page 413) 声明: @Alice 怎么样?我声明了当那些函数包含在 C++ 标准库中时,restrict 将被(排除,被排除在)C 标准库函数签名和语义中。或者换句话说,我陈述了这样一个事实,即如果 C 标准库函数的签名包含 C 中的 restrict,则必须从 C++ 等效的签名中删除 restrict 关键字。 【参考方案1】:

在他的论文 Memory Optimization 中,Christer Ericson 说虽然 restrict 还不是 C++ 标准的一部分,但它受到许多编译器的支持,他建议在可用时使用它:

限制关键字

! 1999 年 ANSI/ISO C 标准的新内容

!还不是 C++ 标准,但许多 C++ 编译器都支持

!只是一个提示,所以可能什么都不做,仍然符合要求

限制限定的指针(或引用)...

! ...基本上是一个 向编译器承诺,对于 指针的范围,指针的目标只会 通过该指针访问(和复制的指针 从它)。

在支持它的 C++ 编译器中,它的行为可能与 C 中的相同。

详情请参阅此 SO 帖子:Realistic usage of the C99 ‘restrict’ keyword?

花半个小时浏览一下 Ericson 的论文,很有趣,值得花时间。

编辑

我还发现了 IBM 的AIX C/C++ compiler supports the __restrict__ keyword。

g++ 似乎也支持这一点,因为以下程序可以在 g++ 上干净地编译:

#include <stdio.h>

int foo(int * __restrict__ a, int * __restrict__ b) 
    return *a + *b;


int main(void) 
    int a = 1, b = 1, c;

    c = foo(&a, &b);

    printf("c == %d\n", c);

    return 0;

我还找到了一篇关于restrict使用的不错的文章:

Demystifying The Restrict Keyword

编辑2

我看到一篇文章专门讨论了在 C++ 程序中使用限制:

Load-hit-stores and the __restrict keyword

还有,Microsoft Visual C++ also supports the __restrict keyword。

【讨论】:

内存优化论文链接已失效,这是他的 GDC 演示文稿中的音频链接。 gdcvault.com/play/1022689/Memory @EnnMichael:显然,如果你打算在可移植的 C++ 项目中使用它,你应该 #ifndef __GNUC__ #define __restrict__ /* no-op */ 或类似的。如果定义了_MSC_VER,则将其定义为__restrict 幻灯片可以在这里找到lukasz.dk/mirror/research-scea/research/pdfs/…【参考方案2】:

正如其他人所说,它意味着与 C++14 无关,所以让我们考虑一下 __restrict__ GCC 扩展,它与 C99 restrict 的作用相同。

C99

restrict 表示两个指针不能指向重叠的内存区域。最常见的用法是用于函数参数。

这限制了函数的调用方式,但允许更多的编译优化。

如果调用者不遵循restrict 合约,可能会发生未定义的行为。

C99 N1256 draft 6.7.3/7“类型限定符”说:

restrict 限定符(如寄存器存储类)的预期用途是促进优化,并且从组成符合程序的所有预处理翻译单元中删除限定符的所有实例不会改变其含义(即,可观察的行为)。

和 6.7.3.1 “限制的正式定义”给出了血淋淋的细节。

可能的优化

Wikipedia example非常很有启发性。

它清楚地展示了如何它允许保存一条汇编指令

无限制:

void f(int *a, int *b, int *x) 
  *a += *x;
  *b += *x;

伪汇编:

load R1 ← *x    ; Load the value of x pointer
load R2 ← *a    ; Load the value of a pointer
add R2 += R1    ; Perform Addition
set R2 → *a     ; Update the value of a pointer
; Similarly for b, note that x is loaded twice,
; because x may point to a (a aliased by x) thus 
; the value of x will change when the value of a
; changes.
load R1 ← *x
load R2 ← *b
add R2 += R1
set R2 → *b

有限制:

void fr(int *restrict a, int *restrict b, int *restrict x);

伪汇编:

load R1 ← *x
load R2 ← *a
add R2 += R1
set R2 → *a
; Note that x is not reloaded,
; because the compiler knows it is unchanged
; "load R1 ← *x" is no longer needed.
load R2 ← *b
add R2 += R1
set R2 → *b

GCC 真的能做到吗?

g++4.8 Linux x86-64:

g++ -g -std=gnu++98 -O0 -c main.cpp
objdump -S main.o

-O0 是一样的。

-O3:

void f(int *a, int *b, int *x) 
    *a += *x;
   0:   8b 02                   mov    (%rdx),%eax
   2:   01 07                   add    %eax,(%rdi)
    *b += *x;
   4:   8b 02                   mov    (%rdx),%eax
   6:   01 06                   add    %eax,(%rsi)  

void fr(int *__restrict__ a, int *__restrict__ b, int *__restrict__ x) 
    *a += *x;
  10:   8b 02                   mov    (%rdx),%eax
  12:   01 07                   add    %eax,(%rdi)
    *b += *x;
  14:   01 06                   add    %eax,(%rsi) 

对于外行来说,calling convention 是:

rdi = 第一个参数 rsi = 第二个参数 rdx = 第三个参数

GCC 输出比 wiki 文章更清晰:4 条指令与 3 条指令。

数组

到目前为止,我们节省了单条指令,但如果指针表示要循环的数组,这是一个常见的用例,那么可以节省一堆指令,正如 supercat 和 michael 所提到的那样。

考虑例如:

void f(char *restrict p1, char *restrict p2, size_t size) 
     for (size_t i = 0; i < size; i++) 
         p1[i] = 4;
         p2[i] = 9;
     
 

由于restrict,智能编译器(或人类)可以将其优化为:

memset(p1, 4, size);
memset(p2, 9, size);

这可能更高效,因为它可能在一个像样的 libc 实现(如 glibc)Is it better to use std::memcpy() or std::copy() in terms to performance? 上进行了汇编优化,可能使用 SIMD instructions。

如果没有限制,则无法进行此优化,例如考虑:

char p1[4];
char *p2 = &p1[1];
f(p1, p2, 3);

然后for版本制作:

p1 == 4, 4, 4, 9

memset 版本使:

p1 == 4, 9, 9, 9

GCC 真的能做到吗?

GCC 5.2.1.Linux x86-64 Ubuntu 15.10:

gcc -g -std=c99 -O0 -c main.c
objdump -dr main.o

-O0 是一样的。

-O3:

有限制:

3f0:   48 85 d2                test   %rdx,%rdx
3f3:   74 33                   je     428 <fr+0x38>
3f5:   55                      push   %rbp
3f6:   53                      push   %rbx
3f7:   48 89 f5                mov    %rsi,%rbp
3fa:   be 04 00 00 00          mov    $0x4,%esi
3ff:   48 89 d3                mov    %rdx,%rbx
402:   48 83 ec 08             sub    $0x8,%rsp
406:   e8 00 00 00 00          callq  40b <fr+0x1b>
                        407: R_X86_64_PC32      memset-0x4
40b:   48 83 c4 08             add    $0x8,%rsp
40f:   48 89 da                mov    %rbx,%rdx
412:   48 89 ef                mov    %rbp,%rdi
415:   5b                      pop    %rbx
416:   5d                      pop    %rbp
417:   be 09 00 00 00          mov    $0x9,%esi
41c:   e9 00 00 00 00          jmpq   421 <fr+0x31>
                        41d: R_X86_64_PC32      memset-0x4
421:   0f 1f 80 00 00 00 00    nopl   0x0(%rax)
428:   f3 c3                   repz retq

两个memset 按预期调用。

没有限制:没有 stdlib 调用,只有 16 次迭代宽 loop unrolling,我不打算在这里重现 :-)

我没有耐心对它们进行基准测试,但我相信限制版本会更快。

严格的别名规则

restrict 关键字只影响兼容类型的指针(例如两个int*),因为严格的别名规则表明,别名不兼容的类型默认情况下是未定义的行为,因此编译器可以假定它不会发生并优化掉。

见:What is the strict aliasing rule?

它是否适用于参考?

根据 GCC 文档,它是:https://gcc.gnu.org/onlinedocs/gcc-5.1.0/gcc/Restricted-Pointers.html,语法:

int &__restrict__ rref

甚至还有this 的成员函数版本:

void T::fn () __restrict__

【讨论】:

不错的回答。如果-fno-strict-aliasing 禁用了严格别名怎么办,那么restrict 应该在相同类型或不同类型的指针之间没有区别,不是吗? (我指的是“restrict 关键字只影响兼容类型的指针”) @tobi303 我不知道!如果您确定,请告诉我 ;-) @jww 是的,这是一种更好的措辞方式。已更新。 restrict 在 C++ 中确实意味着某些东西。如果您从 C++ 程序调用带有 restrict 参数的 C 库函数,则必须遵守其含义。基本上,如果在 C 库 API 中使用 restrict,它对任何从任何语言调用它的人来说都意味着什么,包括来自 Lisp 的动态 FFI。【参考方案3】:

什么都没有。它已被添加到 C99 标准中。

【讨论】:

这并不完全正确。显然,一些 C++ 编译器支持它,有些人强烈推荐它在可用时使用,请参阅下面的答案。 @Robert S Barnes:C++ 标准不将 restrict 识别为关键字。因此我的回答是正确的。您所描述的是特定于实现的行为以及您不应该真正依赖的东西。 @dirkgently:恕我直言,为什么不呢?许多项目都与仅由特定或极少数编译器支持的特定非标准语言扩展相关联。想到 Linux 内核和 gcc。在项目的整个有用生命周期内坚持使用特定的编译器,甚至是特定编译器的特定修订版并不少见。并非每个程序都需要严格遵守。 @Rpbert S. Barnes:我不能再强调为什么你不应该依赖于实现特定的行为。至于 Linux 和 gcc——想想你就会明白为什么它们不是你辩护的好例子。我还没有看到在其生命周期内在单一编译器版本上运行的一个中等成功的软件。 @Rpbert S. Barnes:问题说的是 c++。不是 MSVC,不是 gcc,不是 AIX。如果 acidzombie24 想要编译器特定的扩展,他应该这么说/标记。【参考方案4】:

This 是添加此关键字的原始提议。正如 dirkgently 指出的那样,这是一个 C99 功能;它与 C++ 无关。

【讨论】:

许多 C++ 编译器支持 __restrict__ 关键字,据我所知,这是相同的。 它与C++有一切关系,因为C++程序调用C库,C库使用restrict。如果 C++ 程序的行为违反了 restrict 所暗示的限制,那么它的行为就会变得不确定。 @kaz 完全错误。它与 C++ 无关,因为它不是 C++ 的关键字或特性,如果您在 C++ 中使用 C 头文件,则必须删除 restrict 关键字。当然,如果您将别名指针传递给声明它们受限的 C 函数(您可以从 C++ 或 C 中执行此操作),那么它是未定义的,但这取决于您。 @JimBalter 我明白了,所以你说的是 C++ 程序调用 C 库,而 C 库使用 restrict。如果 C++ 程序的行为违反了 restrict 隐含的限制,则它的行为将变得未定义。但这实际上与 C++ 无关,因为它“在你身上”。【参考方案5】:

由于某些 C 库的头文件使用关键字,C++ 语言将不得不对此做一些事情.. 至少忽略关键字,因此我们不必将关键字#define 为空白宏来禁止关键字。

【讨论】:

我猜这要么通过使用extern C 声明来处理,要么通过静默删除,就像AIX C/C++ 编译器一样,它处理__rerstrict__ 关键字。 gcc 也支持该关键字,因此代码将在 g++ 下编译。【参考方案6】:

C++ 中没有这样的关键字。 C++ 关键字列表可以在 C++ 语言标准的第 2.11/1 节中找到。 restrict 是 C99 版本的 C 语言中的关键字,而不是 C++ 中的关键字。

【讨论】:

许多 C++ 编译器支持 __restrict__ 关键字,据我所知,这是相同的。 @Robert:但是C++ 中没有这样的关键字。各个编译器做的是他们自己的事,但这不是 C++ 语言的一部分。 这样的答案是。它非常有帮助。这可能是对原始问题的评论,但如果问题的真正目的足够明显(restrict 关键字的目的),那么最好在事实的挑剔之后添加真正的答案。

以上是关于C++中的restrict关键字是啥意思?的主要内容,如果未能解决你的问题,请参考以下文章

Android aosp 中的 PACKED 关键字是啥意思?

计算机C语言中的关键字:const是啥意思?

c++ 隐式声明是啥意思

C++中NEW是啥意思呢.急需

C++中typedef是啥意思啊

c++语言中class是啥意思