C ++에서 제한 키워드는 무엇을 의미합니까?


182

나는 항상 확실하지 않았다. C ++에서 restrict 키워드는 무엇을 의미 하는가?

함수에 주어진 둘 이상의 포인터가 겹치지 않는다는 것을 의미합니까? 다른 의미는 무엇입니까?


23
restrictc99 키워드입니다. 네, Rpbert S. Barnes는 대부분의 컴파일러가 지원한다는 것을 알고 __restrict__있습니다. 이중 밑줄이있는 것은 정의에 따라 구현에 따라 다르며 C ++ 이 아니라 컴파일러 특정 버전입니다.
KitsuneYMG

5
뭐? 구현에 따라 C ++이 아닌 것은 아닙니다. C ++은 구현 특정 사항을 명시 적으로 허용하며 C ++을 허용하지 않거나 렌더링하지 않습니다.
Alice

4
@Alice KitsuneYMG는 ISO C ++의 일부가 아니며 대신 C ++ 확장으로 간주됨을 의미합니다. 컴파일러 제작자는 ISO C ++와 공존하며 C ++에 일반적으로 이식성이 낮은 비공식 추가 기능의 일부로 작동하는 자체 확장을 만들어 배포 할 수 있습니다. 예를 들어 MS의 이전 Managed C ++ 및 최신 C ++ / CLI가 있습니다. 다른 예로는 공통 #warning지시어 또는 함수 시그니처 매크로 ( __PRETTY_FUNCTION__GCC, __FUNCSIG__MSVC 등) 와 같은 일부 컴파일러에서 제공하는 전 처리기 지시문 및 매크로가 있습니다 .
저스틴 타임-복원 모니카

4
@Alice 내 지식으로는 C ++ 11은 모든 C99를 완벽하게 지원하거나 C ++ 14 또는 C ++ 17에 대해 아는 것을 요구하지 않습니다. restrictC ++ 키워드 ( 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)에는 다음과 같이 명시되어 있습니다.
Justin Time-Monica Reinstate Monica

4
@ 앨리스 어떻게? 나는 C 표준 라이브러리 함수 시그니처와 의미가 C ++ 표준 라이브러리에 포함될 때 C 표준 라이브러리 함수 시그니처와 시맨틱에서 생략restrict 되어야 한다고 말하는 부분을 언급했다 . 즉, C 표준 라이브러리 함수의 서명 에 C 가 포함 되어 있으면 키워드를 C ++ 등가의 서명에서 제거해야 한다는 사실을 언급했습니다 . restrictrestrict
저스틴 타임-복원 모니카

답변:


143

Christer Ericson은 자신의 논문 인 Memory Optimization 에서 restrict아직 C ++ 표준의 일부는 아니지만 많은 컴파일러에서 지원하고 있으며 사용 가능한 경우이를 사용하도록 권장한다고 말합니다.

키워드 제한

! 1999 ANSI / ISO C 표준에 새로 추가

! C ++ 표준은 아니지만 많은 C ++ 컴파일러에서 지원

! 힌트 만이므로 아무 것도 할 수 없으며 여전히 준수하고 있습니다.

제한 규정 포인터 (또는 참조) ...

! ... 기본적으로 컴파일러는 포인터의 범위에 대해 포인터의 대상이 해당 포인터를 통해 액세스되고 포인터가 포인터에서 복사된다는 약속을합니다.

이를 지원하는 C ++ 컴파일러에서는 아마도 C에서와 동일하게 작동해야합니다.

자세한 내용은이 SO 게시물을 참조하십시오 : C99 '제한'키워드의 현실적인 사용법?

에릭슨의 논문을 훑어 보려면 30 분이 걸립니다. 재미 있고 가치가 있습니다.

편집하다

또한 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 ++ 프로그램에서 제한 사용에 대해 구체적으로 설명하는 기사를 살펴 보았습니다.

적중 상점 및 __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정의하십시오.
Peter Cordes

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 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 리눅스 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);

glibc와 같은 괜찮은 libc 구현에서 어셈블리 최적화 될 수 있으므로 잠재적으로 훨씬 효율적인 방법은 무엇입니까 성능 측면에서 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가 실제로합니까?

GCC 5.2.1. 리눅스 x86-64 우분투 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__

좋은 asnwer. 엄격한 앨리어싱이 비활성화 -fno-strict-aliasing되면 restrict같은 유형 또는 다른 유형의 포인터 사이에 차이가 없어야합니까? ( "restrict 키워드는 호환되는 유형의 포인터에만 영향을 미칩니다"참조)
idclev 463035818


@ jww 예, 그것은 그것을 표현하는 더 좋은 방법입니다. 업데이트되었습니다.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

restrictC ++에서 무언가를 의미합니다. restrictC ++ 프로그램의 매개 변수를 사용하여 C 라이브러리 함수를 호출하는 경우 그 의미를 준수해야합니다. 기본적으로 restrictC 라이브러리 API에서 사용되는 경우 Lisp의 동적 FFI를 포함하여 모든 언어에서 호출하는 사람에게 의미가 있습니다.
Kaz

22

아무것도. C99 표준에 추가되었습니다.


8
그것은 사실이 아닙니다. 분명히 일부 C ++ 컴파일러에서 지원되며 일부 사람들은 사용 가능한 경우 사용법을 강력히 권장합니다. 아래 답변을 참조하십시오.
Robert S. Barnes

18
@Robert S Barnes : C ++ 표준은 restrict키워드로 인식되지 않습니다 . 따라서 내 대답은 정확합니다. 설명하는 것은 구현 특정 동작과 실제로 의존 해서는 안되는 것입니다.
dirkgently 2009

27
@dirkgently : 모든 적절한 존중과 함께, 왜 안됩니까? 많은 프로젝트는 특정 컴파일러 또는 소수의 컴파일러 만 지원하는 특정 비표준 언어 확장과 연결되어 있습니다. 리눅스 커널과 gcc가 떠오른다. 프로젝트의 전체 유효 수명 동안 특정 컴파일러 또는 특정 컴파일러의 특정 개정을 고수하는 것은 드문 일이 아닙니다. 모든 프로그램이 엄격하게 준수해야하는 것은 아닙니다.
Robert S. Barnes

7
@Rpbert S. Barnes : 구현 관련 동작에 의존해서는 안되는 이유를 더 이상 강조 할 수 없습니다. 리눅스와 gcc에 관해서는-당신이 방어에서 좋은 예가 아닌 이유를 생각할 것입니다. 나는 평생 동안 단일 컴파일러 버전 에서 소프트웨어가 적당히 성공적으로 실행되는 것을 보지 못했습니다 .
dirkgently 2009

16
@Rpbert S. Barnes : 질문은 C ++이라고 말했습니다. MSVC는 아니고 gcc도 아니고 AIX도 아닙니다. acidzombie24가 컴파일러 특정 확장을 원한다면 그렇게 말했거나 태그를 지정해야합니다.
KitsuneYMG

12

키워드를 추가하기위한 원래 제안입니다. 그러나 더군다나 지적했듯이 이것은 C99 기능입니다. C ++과는 아무런 관련이 없습니다.


5
많은 C ++ 컴파일러 __restrict__는 내가 알 수 있는 한 키워드를 지원합니다 .
Robert S. Barnes

C ++ 프로그램은 C 라이브러리를 호출하고 C 라이브러리는을 사용하기 때문에 C ++과 관련이 있습니다 restrict. 에 의해 암시 된 제한을 위반하면 C ++ 프로그램의 동작이 정의되지 않습니다 restrict.
Kaz

@kaz 완전히 틀렸다. C ++의 키워드 또는 기능이 아니기 때문에 C ++과는 아무런 관련이 없으며 C ++에서 C 헤더 파일을 사용하는 경우 restrict키워드 를 제거해야합니다 . 물론 C ++ 또는 C에서 수행 할 수있는 제한을 선언하는 C 함수에 별칭 포인터를 전달하면 정의되지 않지만 그 자체가 있습니다.
짐 발터

@JimBalter 알다시피, C ++ 프로그램은 C 라이브러리를 호출하고 C 라이브러리는을 사용한다는 것 restrict입니다. C ++ 프로그램의 동작이 제한에 의해 암시 된 제한을 위반하면 정의되지 않습니다. 그러나 이것은 실제로 "당신"이기 때문에 C ++과는 아무런 관련이 없습니다.
Kaz

5

C ++에는 그러한 키워드가 없습니다. C ++ 키워드 목록은 C ++ 언어 표준의 2.11 / 1 섹션에서 찾을 수 있습니다. restrictC 언어가 아닌 C99 버전의 C 언어로 된 키워드입니다.


5
많은 C ++ 컴파일러 __restrict__는 내가 알 수 있는 한 키워드를 지원합니다 .
Robert S. Barnes

18
@Robert : 그러나 C ++에는 그러한 키워드가 없습니다 . 개별 컴파일러가하는 일은 자체 비즈니스이지만 C ++ 언어의 일부는 아닙니다.
jalf

4

일부 C 라이브러리의 헤더 파일은 키워드를 사용하므로 C ++ 언어는 키워드를 무시하고 최소한 키워드를 무시하므로 키워드를 무시하기 위해 키워드를 빈 매크로로 정의 할 필요가 없습니다. .


3
키워드 extern C를 처리하는 AIX C / C ++ 컴파일러의 경우와 같이 선언 을 사용 하거나 자동으로 삭제 하여 처리한다고 생각합니다 __rerstrict__. 이 키워드는 gcc에서도 지원되므로 코드는 g ++에서 동일하게 컴파일됩니다.
Robert S. Barnes
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.