limit关键字在C ++中是什么意思?


182

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

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


23
restrict是c99关键字。是的,Rpbert S. Barnes,我知道大多数编译器都支持__restrict__。您会注意到,带有双下划线的任何东西,根据定义,都是特定于实现的,因此不是C ++,而是编译器的特定版本。
KitsuneYMG,2010年

5
什么?仅仅因为它特定于实现并不意味着它不是C ++。C ++允许显式地实现特定于实现的东西,而不不允许它或使它不是C ++。
爱丽丝

4
@Alice KitsuneYMG表示它不是ISO C ++的一部分,而是被视为C ++扩展。允许编译器创建者制作和分发自己的扩展程序,这些扩展程序与ISO C ++共存,并且充当C ++通常(或更少或不可移植)非正式添加的一部分。例如,MS的旧托管C ++及其最近的C ++ / CLI。其他示例是一些编译器提供的预处理器指令和宏,例如common #warning指令或功能签名宏(__PRETTY_FUNCTION__在GCC上,__FUNCSIG__在MSVC上等)。
贾斯汀时间-恢复莫妮卡

4
@Alice据我所知,C ++ 11并不要求对所有C99都提供完全支持,C ++ 14或我对C ++ 17的了解也没有。 restrict不被视为C ++关键字(请参阅en.cppreference.com/w/cpp/keyword),实际上,restrictC ++ 11标准中仅提及的内容(请参见open-std.org/jtc1/sc22/wg21 /docs/papers/2012/n3337.pdf,FDIS的副本,进行了较小的编辑更改,第17.2节[library.c],PDF页面413)指出:
贾斯汀时间-恢复莫妮卡

4
@Alice怎么样?我指出,指出该部分restrict将被从省略(从排除的排除在外)C标准库函数签名和语义当这些功能被包括在C ++标准库。换句话说,我说了一个事实,即如果C中包含C标准库函数的签名restrict,则restrict必须从C ++等效签名中删除该关键字。
贾斯汀时间-恢复莫妮卡

Answers:


142

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

限制关键字

!1999 ANSI / ISO C标准的新功能

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

!仅提示,因此可能无济于事,并且仍然合规

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

!...基本上是对编译器的保证,对于指针的范围,只能通过该指针(以及从指针复制的指针)访问指针的目标。

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

有关详细信息,请参见此SO帖子: C99'restrict'关键字的实际用法?

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

编辑

我还发现IBM的AIX C / C ++编译器支持__restrict__关键字

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

揭开限制关键字的神秘面纱

编辑2

我浏览了一篇文章,专门讨论了C ++程序中limit的使用:

负载匹配商店和__restrict关键字

此外,Microsoft Visual C ++ 也支持__restrict关键字


2
Memory Optimization论文链接已失效,这是他GDC演示文稿中音频的链接。gdcvault.com/play/1022689/Memory
Grimeh

1
@EnnMichael:显然,如果要在可移植的C ++项目中使用它,则应该#ifndef __GNUC__ #define __restrict__ /* no-op */或类似的方法。并将其定义为__restrict是否_MSC_VER已定义。
彼得·科德斯

96

就像其他人说的那样,如果从C ++ 14开始没有任何意义,那么让我们考虑__restrict__与C99相同的GCC扩展restrict

C99

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

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

如果呼叫者未遵循restrict合同,则为未定义的行为。

C99 N1256草案 6.7.3 / 7 “类型的限定”说:

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

6.7.3.1“极限的形式定义”给出了详细内容。

可能的优化

维基百科例子非常照明。

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

没有限制:

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 a may be equal to x.
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
load R2  *b
add R2 += R1
set R2  *b

海湾合作委员会真的做到了吗?

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) 

对于未启动的用户,调用约定为:

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

GCC的输出甚至比Wiki文章更清晰:4条指令对3条指令。

数组

到目前为止,我们可以节省一条指令,但是如果指针代表要循环的数组(一种常见的用例),那么可以保存一堆指令,如supercatmichael所提到的。

考虑例如:

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)上进行装配优化,就性能而言,使用std :: memcpy()或std :: copy()更好吗?,可能带有SIMD指令

没有限制,就无法完成此优化,例如,考虑:

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

然后for版本使:

p1 == {4, 4, 4, 9}

而该memset版本使:

p1 == {4, 9, 9, 9}

海湾合作委员会真的做到了吗?

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个迭代宽的循环展开,我不想在这里重现:-)

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

严格的别名规则

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

请参阅:严格的别名规则是什么?

可以参考吗?

根据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在相同类型或不同类型的指针之间应该没有区别,不是吗?(我指的是“
strict

@ tobi303我不知道!让我知道是否可以确定;-)
Ciro Santilli郝海东冠状病六四事件法轮功

@jww是的,这是更好的措辞方式。更新。
Ciro Santilli郝海东冠状病六四事件法轮功

restrict在C ++中确实意味着某些东西。如果restrict使用C ++程序中的参数调用C库函数,则必须遵守该规则的含义。基本上,如果restrict在C库API中使用它,则对于用任何语言调用它的人都意味着某种意义,包括Lisp的动态FFI。
哈兹

22

没有。它已添加到C99标准。


8
这不是完全正确的。显然,某些C ++编译器支持它,并且有人强烈建议在可用时使用它,请参阅下面的答案。
罗伯特·S·巴恩斯

18
@Robert S Barnes:C ++标准无法识别restrict为关键字。因此,我的回答是正确的。您所描述的是特定于实现的行为,是您不应该真正依赖的行为。
2009年

26
@dirkgently:出于所有应有的尊重,为什么不呢?许多项目与仅特定编译器或很少编译器支持的特定非标准语言扩展相关联。我想到了Linux内核和gcc。在项目的整个使用寿命中,始终使用特定的编译器,甚至是特定编译器的特定版本,这种情况并不少见。并非每个程序都必须严格遵循。
罗伯特·S·巴恩斯

7
@Rpbert S. Barnes:我不能再强调为什么您不应该依赖于实现特定的行为了。至于Linux和gcc,请想一想,您会明白为什么它们不是防御方面的好例子。我还没有看到一个相当成功的软件在其整个生命周期内都可以在单个编译器版本上运行。
2009年

16
@Rpbert S. Barnes:问题是c ++。不是MSVC,不是gcc,不是AIX。如果acidzombie24需要编译器特定的扩展,则应该这样说/标记。
KitsuneYMG,2010年

12

是添加此关键字的原始建议。诚然指出,这是C99的功能。它与C ++无关。


5
__restrict__据我所知,许多C ++编译器都支持相同的关键字。
罗伯特·S·巴恩斯

它与C ++有关,因为C ++程序调用C库,而C库使用restrict。如果C ++程序的行为违反隐含的限制,则其行为将变得不确定restrict
哈兹

@kaz完全错误。它与C ++无关,因为它不是C ++的关键字或功能,如果您在C ++中使用C头文件,则必须删除该restrict关键字。当然,如果将别名指针传递给声明其为受限函数的C函数(可以从C ++或C中执行),则它是未定义的,但是就在您身上。
Jim Balter

@JimBalter我知道了,所以您的意思是C ++程序称为C库,而C库使用restrict。如果C ++程序的行为违反限制所隐含的限制,则该行为将变得不确定。但这实际上与C ++无关,因为它“在您身上”。
哈兹

5

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


5
__restrict__据我所知,许多C ++编译器都支持相同的关键字。
罗伯特·S·巴恩斯

17
@Robert:但是在C ++中没有这样的关键字。各个编译器的工作都是自己的事,但这不是C ++语言的一部分。
jalf

4

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


3
我想这可以通过使用extern C声明来处理,也可以通过将其静默删除来实现,就像AIX C / C ++编译器那样,它处理__rerstrict__关键字。gcc还支持该关键字,因此代码将在g ++下进行编译。
罗伯特·S·巴恩斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.