수색…


비고

앨리어싱 규칙을 위반하거나 효과적인 유형의 객체를 위반하는 것은 서로 다른 두 가지이며 혼란스러워서는 안됩니다.

  • 앨리어싱 은 a == b 동일한 객체를 참조 a 두 포인터 a 와 b 의 특성입니다.

  • 데이터 오브젝트의 유효 유형 은 C가 해당 오브젝트에서 수행 할 수있는 조작을 판별하는 데 사용됩니다. 특히, 두 포인터가 서로에 대해 별명을 지정할 수 있는지 여부를 판별하는 데 유효한 유형이 사용됩니다.

하나의 포인터를 통해 객체를 변경하기 때문에 앨리어싱, 최적화 문제가 될 수 말하자면, 다른 포인터를 통해 볼 오브젝트를 변경할 수 a b . C 컴파일러가 포인터가 유형과 출처에 관계없이 항상 별칭을 지정할 수 있다고 가정하면 많은 최적화 기회가 손실되고 많은 프로그램이 느리게 실행됩니다.

C의 엄격한 앨리어싱 규칙은 컴파일러의 케이스가 어떤 오브젝트가 서로 별칭을 지정하는지 (또는 지정하지 않는지)를 가정 할 수 있음을 나타냅니다. 데이터 포인터에는 항상 염두에 두어야 할 엄지 손가락 규칙이 두 가지 있습니다.

달리 언급하지 않는 한, 동일한 기본 유형을 가진 두 개의 포인터가 별명을 지정할 수 있습니다.

두 가지 유형 중 적어도 하나가 문자 유형이 아닌 한, 다른 기본 유형을 가진 두 개의 포인터는 별명을 지정할 수 없습니다.

여기에 기본 형식은 우리가 같은 유형의 자격을 치워 버리고 있다는 것을 의미 const 하는 경우 예를 들어, a 인 double* 및 b 것입니다 const double* , 컴파일러의 변경이 가정 일반적으로해야한다 *a 변경 될 수 있습니다 *b .

두 번째 규칙을 위반하면 치명적인 결과가 발생할 수 있습니다. 여기에서 엄격한 앨리어싱 규칙을 위반한다는 것은 실제로 컴파일러에 다른 유형의 두 포인터 a 와 b 를 제시한다는 것을 의미합니다. 실제로는 동일한 객체를 가리 킵니다. 컴파일러는 항상 다른 개체에 그 두 점을 가정 할 수있다, 그리고 그 생각이 업데이트되지 않습니다 *b 당신을 통해 오브젝트를 변경 한 경우 *a .

그렇게하면 프로그램의 동작이 정의되지 않게됩니다. 따라서 C는 실수로 이러한 상황이 발생하지 않도록 포인터 변환에 매우 심각한 제한을가합니다.

소스 또는 타겟 유형이 void 가 void , 기본 유형이 다른 포인터 간의 모든 포인터 변환은 명시 적 이어야합니다.

즉, 대상 유형에 const 와 같은 한정자를 추가하는 변환을 수행하지 않으면 캐스트 가 필요합니다.

일반적으로 포인터 변환과 캐스트 변환을 피하면 앨리어싱 문제를 방지 할 수 있습니다. 당신이 정말로 필요로하지 않는 한, 이러한 경우는 매우 특별합니다. 가능한 한 피하는 것이 좋습니다.

문자 유형은 비 문자 유형을 통해 액세스 할 수 없습니다.

객체가 정적, 스레드 또는 자동 저장 지속 기간으로 정의되고 문자 유형이 char , unsigned char 또는 signed char 인 경우 비 문자 유형으로 액세스 할 수 없습니다. 아래의 예에서, char 배열 형식으로 재 해석 int , 상기 동작은 모든 비 참조에 정의되지 int 포인터 b .

int main( void )
{
    char a[100];
    int* b = ( int* )&a;
    *b = 1;      

    static char c[100];
    b = ( int* )&c;
    *b = 2;

    _Thread_local char d[100];
    b = ( int* )&d;
    *b = 3;
}

이것은 "유효 유형"규칙을 위반하여 정의되지 않았으므로 유효한 유형의 데이터 객체는 문자 유형이 아닌 다른 유형을 통해 액세스 할 수 없습니다. 다른 유형은 int 이므로 여기에는 허용되지 않습니다.

정렬과 포인터 크기가 잘 맞더라도이 규칙은 면제되지 않지만 동작은 여전히 ​​정의되지 않습니다.

이것은 특히 표준 C에서 malloc 이나 유사한 함수에 의해 수신 된 버퍼를 사용하는 것처럼 다른 유형의 포인터를 통해 사용할 수있는 문자 유형의 버퍼 객체를 예약 할 방법이 없다는 것을 의미합니다.

위의 예와 같은 목표를 달성하는 올바른 방법은 union 를 사용하는 것입니다.

typedef union bufType bufType;
union bufType {
   char c[sizeof(int[25])];
   int i[25];
};

int main( void )
{
    bufType a = { .c = { 0 } }; // reserve a buffer and initialize
    int* b = a.i;      // no cast necessary
    *b = 1;      

    static bufType a = { .c = { 0 } };
    int* b = a.i;
    *b = 2;

    _Thread_local bufType a = { .c = { 0 } };
    int* b = a.i;
    *b = 3;
}

여기서 union 은 처음부터 컴파일러가 다른 뷰를 통해 버퍼에 액세스 할 수 있음을 확인합니다. 이것은 또한 버퍼가 이미 int 타입이고 포인터 변환이 필요없는 "view" ai 를 가지므로 이점이 있습니다.

유효 유형

데이터 오브젝트의 유효 유형 은 연관되어있는 마지막 유형 정보입니다 (있는 경우).

// a normal variable, effective type uint32_t, and this type never changes
uint32_t a = 0.0;

// effective type of *pa is uint32_t, too, simply
// because *pa is the object a
uint32_t* pa = &a;

// the object pointed to by q has no effective type, yet
void* q = malloc(sizeof uint32_t);
// the object pointed to by q still has no effective type,
// because nobody has written to it
uint32_t* qb = q;
// *qb now has effective type uint32_t because a uint32_t value was written
*qb = 37;

// the object pointed to by r has no effective type, yet, although
// it is initialized
void* r = calloc(1, sizeof uint32_t);
// the object pointed to by r still has no effective type,
// because nobody has written to or read from it
uint32_t* rc = r;
// *rc now has effective type uint32_t because a value is read
// from it with that type. The read operation is valid because we used calloc.
// Now the object pointed to by r (which is the same as *rc) has
// gained an effective type, although we didn't change its value.
uint32_t c = *rc;

// the object pointed to by s has no effective type, yet.
void* s = malloc(sizeof uint32_t);
// the object pointed to by s now has effective type uint32_t
// because an uint32_t value is copied into it.
memcpy(s, r, sizeof uint32_t);

후자의 경우, 그 객체에 대한 uint32_t* 포인터가있을 필요는 없다는 사실을 관찰하십시오. 우리가 다른 uint32_t 객체를 복사했다는 사실만으로도 충분합니다.

엄격한 앨리어싱 규칙을 위반합니다.

다음 코드에서는 float 와 uint32_t 의 크기가 동일하다는 것을 단순하게 가정합니다.

void fun(uint32_t* u, float* f) {
    float a = *f
    *u = 22;
    float b = *f;
    print("%g should equal %g\n", a, b);
}

u 와 f 는 서로 다른 기본 유형을 가지므로 컴파일러는 서로 다른 객체를 가리킨다 고 가정 할 수 있습니다. *f 가 a 와 b 의 두 초기화 사이에서 바뀔 수있는 가능성은 없으므로 컴파일러는 코드를 다음과 같은 것으로 최적화 할 수 있습니다.

void fun(uint32_t* u, float* f) {
    float a = *f
    *u = 22;
    print("%g should equal %g\n", a, a);
}

즉, *f 의 두 번째로드 연산은 완전히 최적화 될 수 있습니다.

이 함수를 "보통"

 float fval = 4;
 uint32_t uval = 77;
 fun(&uval, &fval);

모두 잘되고 뭔가 좋아.

4는 4와 같아야합니다.

인쇄됩니다. 그러나 우리가 속여서 동일한 포인터를 전달하면 그것을 변환 한 후,

 float fval = 4;
 uint32_t* up = (uint32_t*)&fval;
 fun(up, &fval);

엄격한 앨리어싱 규칙을 위반합니다. 그러면 동작이 정의되지 않게됩니다. 컴파일러가 두 번째 액세스 또는 완전히 다른 것을 최적화 한 경우 출력이 위와 같을 수 있으므로 프로그램이 완전히 신뢰할 수없는 상태가됩니다.

자격을 제한하다

동일한 유형의 포인터 인수가 2 개있는 경우 컴파일러는 어떤 가정도 할 수 없으며 항상 *e 변경 사항이 *f 변경 될 수 있다고 가정해야합니다.

void fun(float* e, float* f) {
    float a = *f
    *e = 22;
    float b = *f;
    print("is %g equal to %g?\n", a, b);
}

float fval = 4;
float eval = 77;
 fun(&eval, &fval);

모두 잘되고 뭔가 좋아.

4가 4와 같은가?

인쇄됩니다. 동일한 포인터를 전달하면 프로그램은 여전히 ​​올바른 작업을 수행하고 인쇄합니다.

4가 22와 같습니까?

e 와 f 가 같은 데이터 객체를 가리 키지 않는다는 것을 외부 정보를 통해 알면 비효율적 일 수 있습니다. 포인터 매개 변수에 restrict 한정자를 추가하여 해당 지식을 반영 할 수 있습니다.

void fan(float*restrict e, float*restrict f) {
    float a = *f
    *e = 22;
    float b = *f;
    print("is %g equal to %g?\n", a, b);
}

그렇다면 컴파일러는 항상 e 와 f 가 다른 객체를 가리킨다 고 가정합니다.

바이트 변경

객체가 유효한 유형을 가지면 다른 유형이 문자 유형, char , signed char 또는 unsigned char 가 아닌 한 다른 유형의 포인터를 통해 수정하려고 시도하면 안됩니다.

#include <inttypes.h>
#include <stdio.h>

int main(void) {
  uint32_t a = 57;
  // conversion from incompatible types needs a cast !
  unsigned char* ap = (unsigned char*)&a;
  for (size_t i = 0; i < sizeof a; ++i) {
    /* set each byte of a to 42 */
    ap[i] = 42;
  }
  printf("a now has value %" PRIu32 "\n", a);
}

이것은 인쇄하는 유효한 프로그램입니다.

707406378 값이 있습니다.

이 이유는 다음과 같습니다.

  • unsigned char 유형으로 표시된 개별 바이트에 대한 액세스가 이루어 지므로 각 수정 사항이 잘 정의됩니다.
  • a 및 through *ap 별칭을 통해 객체에 대한 두 개의보기가 있지만 ap 는 문자 유형에 대한 포인터이기 때문에 엄격한 별칭 지정 규칙이 적용되지 않습니다. 따라서 컴파일러는 a 의 값이 for 루프에서 변경되었을 수 있다고 가정해야합니다. 의 수정 된 값 a 변경된 바이트로 구성되어야한다.
  • 의 타입 a , uint32_t 패딩 비트가 없다. 표현의 모든 비트 (여기서는 707406378 )는 트랩 표현이 될 수 없습니다.


Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow