무엇보다도 C99는 프로그래머가 포인터가 범위 내 주어진 객체에 대한 전용 포인터임을 지정하고 결과적으로 컴파일러에 "힌트"를 제공하는 방법으로 제한 키워드를 추가했습니다. ” 해당 포인터를 통해 객체에 액세스할 때 추가 최적화를 수행할 수 있습니다.
restrict가 해결하려는 문제를 설명하려면 다음과 같은 함수를 고려하세요.
void update_ptrs( int *p, int *q, int const *v ) { *p = *v; *q = *v; }
컴파일러는 다음과 같은 x86-64 코드를 생성합니다.
mov eax, [rdx] ; tmp = *v // 1 add [rdi], eax ; *p = tmp mov eax, [rdx] ; tmp = *v // 3 add [rsi], eax ; *q = tmp
라인 1과 중복되는 것처럼 보이는 라인 3을 생성하는 이유가 궁금할 것입니다. 문제는 컴파일러가 다음과 같은 작업을 수행하지 않았다는 것을 알 수 없다는 것입니다.
int x = 1, v = 2; update_ptrs( &v, &x, &v ); // x = 5, v = 4
update_ptrs()에서 p와 v는 동일 int를 별칭하므로 컴파일러는 안전하게 작동해야 하며 *v의 값이 읽기 사이에 변경될 수 있다고 가정해야 합니다. 따라서 추가 mov 명령이 필요합니다.
일반적으로 C의 포인터는 최적화를 혼란스럽게 합니다. 컴파일러는 두 포인터가 서로 별칭인지 여부를 알 수 없기 때문입니다. 성능이 중요한 코드에서 메모리 읽기를 제거하는 것은 할 수 큰 승리가 될 수 있습니다. 만약 컴파일러가 이를 안전하게 수행할 수 있습니다.
앞서 언급한 문제를 해결하기 위해 주어진 포인터가 포인터 범위에 있는 객체에 대한 유일 포인터임을 지정할 수 있도록 제한이 C에 추가되었습니다. 즉, 동일한 범위 별칭의 다른 포인터는 없습니다. 그것.
restrict를 사용하려면 선언에서 *와 포인터 이름 사이에 를 삽입합니다. 제한을 사용하기 위해 다시 작성된 update_ptrs()는 다음과 같습니다:
void update_ptrs_v2( int *restrict p, int *restrict q, int const *restrict v ) { *p = *v; *q = *v; }(오른쪽에서 왼쪽으로 읽습니다. 예를 들어 v는 상수 int에 대한 제한된 포인터입니다. 또는 cdecl을 사용합니다.)
제한을 추가함으로써 이제 컴파일러는 다음과 같은 코드를 생성할 수 있습니다.
void update_ptrs_v2( int *restrict p, int *restrict q, int const *restrict v ) { *p = *v; *q = *v; }이제 컴파일러는 추가 mov 명령의 이전 라인 3을 제거할 수 있었습니다.
restrict가 사용되는 가장 잘 알려진 예는 아마도 표준 라이브러리 함수 memcpy()입니다. 소스 주소와 대상 주소가
겹치지 않는 경우 메모리 덩어리를 복사하는 가장 빠른 방법입니다. 약간 느린 memmove() 함수는 주소 do가 겹칠 때 사용하기 위해 존재합니다. 함정
서로 별칭을 지정하는 포인터를 update_ptrs_v2() 또는 memcpy()에 전달합니다. 일부 경우에 컴파일러는 경고할 수 있지만 모든 경우에 그런 것은 아니므로 잘못된 사용을 잡기 위해 컴파일러에 의존하지 마십시오. 제한은 특정 범위에 대한 것입니다. 하나의 제한된 포인터를
동일 범위의 다른 포인터에 할당하면 다음과 같은 정의되지 않은 동작이 발생합니다.
void f( int *restrict d, int *restrict s ) {
int *restrict p = s; // 정의되지 않은 동작
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; // 좋아요
void f( int *restrict d, int *restrict s ) { int *p = s; // OK
내부 범위의 제한된 포인터를 외부 범위의 다른 포인터에 할당하는 것도 괜찮습니다(그러나 그 반대는 안 됨):
void f( int *restrict d, int *restrict s ) {
{ // 내부 범위
int *restrict p = s; // 좋아요
// ...
s = p; // 정의되지 않은 동작
}
}
void f( int *restrict d, int *restrict s ) { { // inner scope int *restrict p = s; // OK // ... s = p; // undefined behavior } }
성능이 향상되어 잠재적 위험을 감수할 수 있는지 확인해야 합니다. 제한을 잘못 사용하여 발생한 버그를 진단하는 것은 매우 어렵습니다. 둘째, 제한 사용이 제한된 포인터를 통해 액세스된 메모리가
당신에 의해 할당된 함수의 구현으로 제한된다면 더 안전합니다. 예를 들어, 다음과 같습니다:
무효 안전( unsigned n ) {
n = n % 2 != 0; // 반올림하여 짝수로 만듭니다.
int *const array = malloc(n * sizeof(unsigned) );
부호 없음 *제한 half_1st = 배열;
unsigned *restrict half_2nd = 배열 n/2;
// ...
무료(배열);
}
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 ); }
셋째, 제한이 함수의 매개변수에 사용되면 잠재적으로
덜안전합니다. 예를 들어 safer()는 호출자가 포인터를 제어하는 update_ptrs_v2()와 대조됩니다. 당신이 알고 있는 모든 것에 대해 발신자는 그것을 잘못 알아차리고 별칭을 붙인 포인터를 전달했습니다. 여러 가지 잡다한
객체(또는 void)에 대한 포인터만 제한:으로 한정할 수 있습니다.
int x 제한; // 오류: 개체를 제한할 수 없습니다.
int 제한 *p; // 오류: 객체를 제한하기 위한 포인터
int (*f 제한)(); // 오류: 함수 포인터
restrict int x; // error: can't restrict object int restrict *p; // error: pointer to restrict object int (*restrict f)(); // error: pointer-to-function
구조체 노드 {
무효 *데이터 제한;
구조체 노드 *왼쪽 제한;
구조체 노드 *권한 제한;
};
struct node { void *restrict data; struct node *restrict left; struct node *restrict right; };
마지막으로 C에는 제한이
없습니다. 왜 안 돼? 답변이 길지만 TL;DR 버전은 다음과 같습니다.
C 위원회가 C에서 가져오기를 원하지 않는 찾기 어려운 버그의 소스가 될 수 있습니다.결론
현명하게 사용하세요.
부인 성명: 제공된 모든 리소스는 부분적으로 인터넷에서 가져온 것입니다. 귀하의 저작권이나 기타 권리 및 이익이 침해된 경우 자세한 이유를 설명하고 저작권 또는 권리 및 이익에 대한 증거를 제공한 후 이메일([email protected])로 보내주십시오. 최대한 빨리 처리해 드리겠습니다.
Copyright© 2022 湘ICP备2022001581号-3