수색…


비고

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

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

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

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

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

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

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

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

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

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

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

즉, 대상 유형에 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 객체를 복사했다는 사실만으로도 충분합니다.

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

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

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

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

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와 같습니까?

ef 가 같은 데이터 객체를 가리 키지 않는다는 것을 외부 정보를 통해 알면 비효율적 일 수 있습니다. 포인터 매개 변수에 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);
}

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

바이트 변경

객체가 유효한 유형을 가지면 다른 유형이 문자 유형, 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