programing

엄격한 별칭 규칙이 잘못 지정되었습니까?

telebox 2023. 10. 24. 21:16
반응형

엄격한 별칭 규칙이 잘못 지정되었습니까?

앞서 설정한 바와 같이 형식의 결합,

union some_union {
    type_a member_a;
    type_b member_b;
    ...
};

n개의 부재는 n개 + 1개의 물체로 구성되어 있습니다.조합 자체를 위한 하나의 객체와 조합원을 위한 하나의 객체.마지막으로 쓴 조합원이 아닌 조합원을 읽더라도 어떤 순서로든 자유롭게 조합원에게 읽고 쓸 수 있음은 분명합니다.스토리지에 액세스하는 l 값의 유효 유형이 정확하므로 엄격한 앨리어싱 규칙을 위반하지 않습니다.

이것은 각주 95에 의해 더욱 뒷받침되는데, 각주 95는 유형 처벌이 노조의 의도적인 사용 방법을 설명합니다.

엄격한 앨리어싱 규칙에 의해 활성화되는 최적화의 대표적인 예는 다음과 같은 기능입니다.

int strict_aliasing_example(int *i, float *f)
{
    *i = 1;
    *f = 1.0;
    return (*i);
}

컴파일러가 다음과 같이 최적화할 수 있습니다.

int strict_aliasing_example(int *i, float *f)
{
    *i = 1;
    *f = 1.0;
    return (1);
}

왜냐하면 그것은 안전하게 다음에 쓰는 것이라고 가정할 수 있기 때문입니다.*f값에 영향을 주지 않습니다.*i.

하지만 같은 조합원들에게 두 가지 지침을 전달하면 어떤 일이 벌어질까요?다음과 같은 일반적인 플랫폼을 가정할 때, 이 예를 생각해 보십시오.float는 IEEE 754 단일 정밀 부동 소수점 번호 및int는 32비트 2의 보 정수입니다.

int breaking_example(void)
{
    union {
        int i;
        float f;
    } fi;

    return (strict_aliasing_example(&fi.i, &fi.f));
}

앞서 본 바와 같이,fi.i그리고.fi.f중복되는 메모리 영역을 참조합니다.그것들을 읽고 쓰는 것은 어떤 순서로든 무조건 합법적입니다(조합이 설립된 후에만 합법적입니다).제 생각에 앞서 논의된 모든 주요 컴파일러에 의해 수행된 최적화는 법적으로 다른 유형의 두 포인터가 동일한 위치를 가리키기 때문에 잘못된 코드를 산출합니다.

나는 어쩐지 엄격한 별칭 규칙에 대한 나의 해석이 정확하다는 것을 믿을 수 없습니다.앞서 언급한 코너 케이스 때문에 엄격한 앨리어싱이 설계된 바로 그 최적화가 불가능할 것 같습니다.

제가 왜 틀렸는지 말해주세요.

연구 중에 관련된 질문이 나타났습니다.

기존의 답변과 의견을 모두 읽고 자신의 답변을 추가하여 답변이 새로운 주장을 추가할 수 있도록 하십시오.

예부터 시작합니다.

int strict_aliasing_example(int *i, float *f)
{
    *i = 1;
    *f = 1.0;
    return (*i);
}

우선 노조가 없는 상황에서 이는 엄격한 앨리어싱 규칙에 위배된다는 것을 인정합니다.i그리고.f두 개체가 동일한 개체를 가리킵니다. 개체에 선언된 유형이 없다고 가정하면,*i = 1유효 유형을 다음으로 설정합니다.int그리고.*f = 1.0다음으로 설정합니다.float, 그리고 결승전.return (*i)그런 다음 효과적인 유형으로 객체에 접근합니다.float활자의 값으로int, 분명히 허용되지 않는 것입니다

문제는 이것이 여전히 엄격한 앨리어싱 위반에 해당하는지 여부입니다.i그리고.f같은 조합원들을 가리킵니다.그렇지 않으려면 이 상황에서 적용되는 엄격한 앨리어싱 규칙에 특별한 예외가 있거나 다음을 통해 객체에 접근해야 합니다.*i다음과 같은 개체에 액세스하지 않습니다.*f.

조합원 접속 운영자를 통한 조합원 접속 시 표준은 다음과 같이 기술합니다(6.5.2.3).

. 연산자와 식별자 뒤에 나오는 후 수정 식은 구조 또는 조합 개체의 멤버를 나타냅니다.값은 명명된 부재(95)의 값이며, 첫 번째 식이 l 값일 경우 l 값입니다.

위에서 언급한 각주 95는 다음과 같이 말합니다.

조합 대상물의 내용을 판독하는 데 사용된 부재가 해당 대상물에 가치를 저장하는 데 사용된 부재와 동일하지 않은 경우에는 6.2.6.에 기술된 새로운 유형("유형 처벌"이라고도 함)에서 해당 가치의 객체 표현의 적절한 부분을 객체 표현으로 재해석합니다.트랩 표현일 수도 있습니다.

이는 명백히 조합을 통한 유형 처벌을 허용하기 위한 것이지만, (1)각주는 비규범적인 것, 즉 행위를 금지하기 위한 것이 아니라, 나머지 명세서에 따라 본문의 일부 부분의 의도를 명확히 해야 하고, (2)조합을 통한 유형 처벌의 허용은컴파일러 공급업체는 조합원 액세스 연산자를 통한 액세스만 신청하는 것으로 간주합니다. 그렇지 않으면 엄격한 앨리어싱은 최적화에 상당히 쓸모가 없기 때문입니다. 단 두 개의 포인터가 동일한 조합의 다른 구성원을 잠재적으로 지칭하기 때문입니다(당신의 예가 대표적인 예입니다).

그래서 이 시점에서 우리는 이렇게 말할 수 있습니다.

  • 예제의 코드는 normative가 아닌 각주에 의해 명시적으로 허용됩니다.
  • 반면에 규범 텍스트는 (엄격한 별칭 때문에) 당신의 예를 허용하지 않는 것처럼 보이는데, 한 조합의 구성원에 접근하는 것이 다른 조합원에 대한 접근을 구성한다고 가정합니다 - 그러나 이것에 대한 더 많은 것.

그런데 실제로 한 조합원에게 접근하는 것이 다른 조합원들에게 접근하는 것입니까?만약 그렇지 않다면, 엄격한 앨리어싱 규칙은 예와 관계가 없습니다. (만약 그렇다면, 엄격한 앨리어싱 규칙은, 문제적으로, 조합을 통한 유형 처벌을 거의 허용하지 않습니다.

연합은 다음과 같이 정의됩니다(6.2.5 par 20).

조합 유형은 비어 있지 않은 멤버 개체의 중복된 집합을 설명합니다.

참고 (6.7.2.1항 16):

언제든지 조합 개체에 구성원 중 하나 이상의 값을 저장할 수 있습니다.

액세스는 (3)이므로:

개체의 값을 읽거나 수정하는 〈execution 시간 작업 〉

... 그리고 비활동적인 조합원은 저장된 값이 없기 때문에 한 조합원에게 접근하는 것이 다른 조합원에게 접근하는 것을 구성하지 않습니다!

그러나 멤버 액세스의 정의(위에서 인용한 6.5.2.3)는 "이름이 지정된 멤버의 값입니다"라고 말합니다(이것이 각주 95가 첨부된 정확한 문장입니다) - 만약 멤버가 값이 없다면 어떻게 합니까?각주 95는 답을 주지만, 제가 언급한 것처럼 그것은 규범 텍스트에 의해 뒷받침되지 않습니다.

어떤 경우에도, 텍스트의 어떤 것도 "멤버 객체를 통해" 유니언 멤버를 읽거나 수정하는 것(즉, 멤버 액세스 연산자를 사용한 표현을 통해 직접)은 동일한 멤버에 대한 포인터를 통해 읽거나 수정하는 것과 다를 것이 없습니다.컴파일러 공급업체가 적용하는 합의적 이해는 다른 유형의 포인터가 별칭이 아니라는 가정 하에 최적화를 수행할 수 있으며, 멤버 액세스를 포함하는 식을 통해서만 유형 펀닝을 수행해야 한다는 가정 하에서 표준의 텍스트에서는 지원되지 않습니다.

각주 95가 규범적인 것으로 간주된다면, 당신의 예는 정의되지 않은 행동 없이 완벽하게 괜찮은 코드입니다. (만약 값이 다음과 같은 경우가 아니라면)(*i)는 트랩 표현입니다). 나머지 텍스트에 따르면.그러나 각주 95가 규범적인 것으로 간주되지 않는 경우 저장된 값이 없는 개체에 대한 액세스 시도가 발생하여 동작이 불확실합니다(엄격한 앨리어싱 규칙은 거의 관련이 없음).

현재 컴파일러 공급업체를 이해하는 데 있어, 당신의 예는 정의되지 않은 동작을 가지고 있지만, 이것이 표준에 명시되어 있지 않기 때문에 코드가 위반하는 제약 조건이 정확히 무엇인지는 명확하지 않습니다.

개인적으로, 표준에 대한 "수정"은 다음과 같습니다.

  • 회원 접근 표현의 value 변환 또는 좌변이 회원 접근 표현인 경우 할당을 통한 비활동 조합원 접근을 허용하지 않음(이에 대한 예외는 해당 조합원이 문자 유형을 가지고 있는 경우에 이루어질 수 있음).엄격한 앨리어싱 규칙 자체의 유사한 예외로 인해 가능한 최적화에 영향을 미치지 않기 때문입니다.)
  • 비활동 멤버의 값이 각주 95에 의해 현재 설명된 대로임을 규범 텍스트에 명시합니다.

예를 들어, 엄격한 별칭 규칙을 위반하는 것이 아니라 비활동적인 조합원은 구성원 액세스 운영자(및 적절한 구성원)가 포함된 표현을 통해서만 액세스해야 한다는 제약 조건을 위반하는 것입니다.

따라서 질문에 답하기 위해 - 엄격한 앨리어싱 규칙이 잘못 지정되었습니까? - 아니요, 두 포인터 참조로 액세스하는 개체는 별개의 개체이고 스토리지에서 중복되더라도 한 번에 하나의 값을 가지므로 엄격한 앨리어싱 규칙은 이 예제와 관련이 없습니다.그러나 조합원 접근 규칙이 잘못 지정되어 있습니다.

결함 보고서 236에 대한 참고 사항:

조합 의미론에 관한 논쟁은 항상 어떤 시점에서 DR 236을 참조합니다.실제로 귀하의 예제 코드는 표면적으로 해당 결함 보고서의 코드와 매우 유사합니다.다음 사항에 유의하겠습니다.

  1. DR 236의 예는 타입 펀칭에 관한 것이 아닙니다.활동하지 않는 조합원에게 해당 조합원에 대한 포인터를 통해 할당해도 되는지에 대한 것입니다.두 번째 조합원에게 글을 쓴 후 '원본' 조합원에게 다시 접속을 시도하지 않기 때문에 문제의 코드는 여기서 문제의 코드와 미묘하게 다릅니다.따라서 예제 코드의 구조적 유사성에도 불구하고 결함 보고서는 귀하의 질문과 대부분 관련이 없습니다.
  2. "위원회는 사례 2가 6.5항 7의 앨리어싱 규칙을 위반한다고 생각합니다." - 이는 위원회가 "비활동적인" 조합원을 적지만 조합 대상에 대한 조합원 접근이 포함된 표현을 사용하지 않는 것은 엄격한 앨리어싱 위반이라고 생각한다는 것을 나타냅니다.위에서 자세히 설명했듯이, 이것은 표준의 본문에서 지원되지 않습니다.
  3. "규칙을 위반하지 않으려면 예를 들어 함수 f를 다음과 같이 써야 합니다." - 즉, 조합 개체(및 ". 연산자"를 사용하여 활성 멤버 유형을 변경해야 합니다. 이는 위에서 제안한 표준에 대한 "fix"와 일치합니다.
  4. DR 236의 위원회 응답은 "두 프로그램 모두 정의되지 않은 동작을 호출한다"고 주장합니다.그것은 첫 번째가 왜 그렇게 하는지에 대한 설명이 없고, 두 번째가 왜 그렇게 하는지에 대한 설명은 틀린 것 같습니다.

§6.5.2.3의 조합원 정의에 따라:

3 사후 수정 표현에 이어.연산자와 식별자는 구조물 또는 유니언 오브젝트의 멤버를 지정합니다. ...

4 사후 수정 표현에 이어->연산자와 식별자는 구조물 또는 유니언 오브젝트의 멤버를 지정합니다. ...

§6.2.31 참조:

  • 구조 또는 조합의 구성원; 각각의 구조 또는 조합은 구성원을 위한 별도의 이름 공간을 가지고 있습니다 (구성원에게 접근하기 위해 사용되는 표현의 유형으로 모호함)..아니면->연산자);

각주95는 조합원이 조합을 범위로 하여 접근하고 그 범위를 사용하는 것을 의미하는 것임이 분명합니다..아니면->교환입니다.

조합을 구성하는 바이트에 대한 할당과 액세스는 조합원을 통해 이루어지는 것이 아니라 포인터를 통해 이루어지므로, 프로그램에서 조합원의 앨리어싱 규칙(각주 95에 의해 명시된 규칙 포함)을 호출하지 않습니다.

또한 다음과 같은 객체의 유효한 유형이 있으므로 일반적인 앨리어싱 규칙을 위반합니다.*f = 1.0이다.float, 그러나 저장된 값은 유형의 모든 값에 의해 액세스됩니다.int 6.57). § 6.57 참조).

참고: 모든 참고 문헌은 C11 표준 초안을 인용합니다.

C11 표준( §6.5.2.3.9 예제 3)의 예는 다음과 같습니다.

다음은 유효한 조각이 아닙니다(함수 f 내에서 유니언 유형이 보이지 않기 때문에:

 struct t1 { int m; };
 struct t2 { int m; };
 int f(struct t1 *p1, struct t2 *p2)
 {
       if (p1->m < 0)
               p2->m = -p2->m;
       return p1->m;
 }
 int g()
 {
       union {
               struct t1 s1;
               struct t2 s2;
       } u;
       /* ... */
       return f(&u.s1, &u.s2);
 }

하지만 이에 대해서는 더 이상 명확한 설명을 찾을 수가 없습니다.

기본적으로 엄격한 앨리어싱 규칙은 컴파일러가 서로 다른 유형의 두 포인터가 메모리의 동일한 위치를 가리키지 않는다고 가정할 수 있는 상황을 설명합니다.

이를 바탕으로, 당신이 설명하는 최적화는strict_aliasing_example()컴파일러가 다음을 가정할 수 있기 때문에 허용됩니다.f그리고.i다른 주소를 가리킵니다.

breaking_example()두 포인터가 에 전달되도록 합니다.strict_aliasing_example()같은 주소를 가리키게 됩니다.이것은 그들이 그들에게strict_aliasing_example()를 만드는 것이 허용되므로 함수가 정의되지 않은 동작을 나타내는 결과가 됩니다.

그래서 당신이 설명한 컴파일러 동작은 유효합니다.는 사실입니다.breaking_example()포인터가 에 전달되도록 합니다.strict_aliasing_example()정의되지 않은 행동을 유발하는 동일한 주소를 가리킴 - 즉,breaking_example()컴파일러가 다음과 같이 만들 수 있다는 가정을 깨뜨립니다.strict_aliasing_example().

엄격한 앨리어싱 규칙은 문자 유형에 대한 포인터가 아닌 한 호환되지 않는 두 개의 포인터로 동일한 개체에 액세스하는 것을 금지합니다.

7 객체는 다음 중 하나의 유형을 가지는 l 값 표현에 의해서만 저장된 값에 접근합니다. 88)

  • 객체의 유효한 유형과 호환되는 유형,
  • 물체의 유효한 형식과 호환되는 형식의 한정된 버전,
  • 개체의 유효 유형에 해당하는 서명 또는 비부호 유형인 유형,
  • 유효한 개체 유형의 한정된 버전에 해당하는 서명된 또는 비부호된 유형.
  • 구성원 중에서 앞서 언급한 유형 중 하나를 포함하는 집합체 또는 조합 유형(재귀적으로 하위 집합체 또는 포함된 조합의 구성원 포함) 또는
  • 성격 타입

,*f = 1.0;수정 중입니다.fi.i, 하지만 유형이 호환되지 않습니다.

실수는 조합이 n개의 객체를 포함하고, n은 조합원 수라고 생각하는 것 같습니다.유니언은 §6.7.2.116에 의해 프로그램이 실행되는 동안 어느 한 지점에 하나의 활성 개체만 포함합니다.

언제든지 조합 개체에 구성원 중 하나 이상의 값을 저장할 수 있습니다.

조합이 모든 조합원 개체를 동시에 포함하지 않는다는 해석을 뒷받침하는 자료는 §6.5.2.3에서 찾을 수 있습니다.

그리고 유니언 오브젝트가 현재 이러한 구조 중 하나를 포함하고 있는 경우

결국 2006년 결함신고 236호에서도 거의 동일한 문제가 제기되었습니다.

예제2

// optimization opportunities if "qi" does not alias "qd"
void f(int *qi, double *qd) {
    int i = *qi + 2;
    *qd = 3.1;       // hoist this assignment to top of function???
    *qd *= i;
    return;
}  

main() {
    union tag {
        int mi;
        double md;
    } u;
    u.mi = 7;
    f(&u.mi, &u.md);
}

위원회는 사례 2가 6.5 문단 7의 앨리어싱 규정을 위반한다고 보고 있습니다.

"조합원 중에서 앞서 언급한 유형 중 하나를 포함하는 집합체 또는 조합 유형(재귀적으로 하위 집합체 또는 포함된 조합원 포함)."

규칙을 위반하지 않으려면 예를 들어 함수 f를 다음과 같이 써야 합니다.

union tag {
    int mi;
    double md;
} u;

void f(int *qi, double *qd) {
    int i = *qi + 2;
    u.md = 3.1;   // union type must be used when changing effective type
    *qd *= i;
    return;
}

여기 노트 95와 그 맥락이 있습니다.

. 연산자와 식별자 뒤에 나오는 후 수정 식은 구조 또는 조합 개체의 멤버를 나타냅니다.값은 명명된 멤버의 값이며, 첫 번째 식이 l 값일 경우 l 값입니다.첫 번째 식에 정규화된 유형이 있으면 지정된 구성원 유형의 정규화된 버전이 결과에 나타납니다.

(95) 조합 대상의 내용을 읽는 데 사용된 부재가 해당 대상에 값을 저장하는 데 사용된 부재와 동일하지 않으면 6.2.6에서 설명한 새로운 유형의 대상 표현 중 적절한 부분을 대상 표현으로 재해석합니다(종종 "type punning"이라고도 함).트랩 표현일 수도 있습니다.

주 95는 조합원을 통한 접근에 명백하게 적용됩니다.당신의 코드는 그렇게 하지 않습니다.두 개의 다른 유형에 대한 포인터를 통해 두 개의 중복된 개체에 액세스하며, 그 중 어떤 개체도 문자 유형이 아니며, 유형 펀닝과 관련된 후 수정 표현도 없습니다.

이것은 확실한 대답이 아닙니다...

표준에서 잠시 벗어나 컴파일러가 실제로 가능한 것이 무엇인지 생각해 보겠습니다.

예를 들면strict_aliasing_example()에 정의됩니다.strict_aliasing_example.c,그리고.breaking_example()에 정의됩니다.breaking_example.c. 다음과 같이 두 파일이 별도로 컴파일된 다음 함께 연결된다고 가정합니다.

gcc -c -o strict_aliasing_example.o strict_aliasing_example.c
gcc -c -o breaking_example.o breaking_example.c
gcc -o breaking_example strict_aliasing_example.o breaking_example.o

물론 기능 프로토타입을 추가해야 합니다.breaking_example.c, 다음과 같이 보입니다.

int strict_aliasing_example(int *i, float *f);

이제 첫번째 두개의 호출을 고려됩니다.gcc완전히 독립적이며 기능 프로토타입 외에는 정보를 공유할 수 없습니다.컴파일러가 그것을 아는 것은 불가능합니다.i그리고.j다음에 대한 코드를 생성할 때 동일한 조합의 구성원을 가리킬 것입니다.strict_aliasing_example()에는 이 것이기 에 어떤 할 수 것이 링크나 타입 시스템에는 조합에서 나온 것이기 때문에 이 포인터들이 어떤 식으로든 특별하다는 것을 명시할 수 있는 것이 없습니다.

이는 다른 답변이 언급한 결론을 뒷받침합니다. 즉, 표준의 관점에서 다음을 통해 조합에 접근하는 것입니다..아니면->는 임의의 포인터를 재참조하는 것과 비교하여 다른 별칭 규칙을 준수합니다.

C89 표준 이전에는 대부분의 구현이 특정 유형의 포인터에 대한 쓰기-비참조 동작을 해당 유형에 대해 정의된 방식으로 기본 저장소의 비트를 설정하는 것으로 정의했습니다.특정 유형의 포인터를 읽기 dere 참조하는 동작을 해당 유형에 대해 정의된 방식으로 기본 저장소의 비트를 읽기로 정의했습니다.이러한 기능이 모든 구현에서 유용하지는 않겠지만, 32비트 로드와 저장소를 사용하여 4바이트 그룹에서 동시에 작동하는 등 핫 루프의 성능을 크게 향상시킬 수 있는 구현이 많이 있었습니다.또한, 이러한 많은 구현에서 이러한 동작을 지원하는 데에는 비용이 들지 않았습니다.

C89 Standard의 저자들은 그들의 목적 중 하나가 기존의 코드를 회복할 수 없게 깨지는 것을 피하는 것이었고, 규칙이 그것과 일치하도록 해석될 수 있었던 두 가지 근본적인 방법이 있다고 말합니다.

  1. C89 규칙은 이론적 근거에 주어진 것과 유사한 경우(해당 유형을 통해 직접 또는 포인터를 통해 선언된 유형의 객체에 접근), 컴파일러가 l 값이 연관되어 있다고 예상할 이유가 없는 경우에만 적용할 수 있습니다.각 변수가 현재 레지스터에 캐시되어 있는지 여부를 추적하는 것은 매우 간단하며, 다른 유형의 포인터에 액세스하는 동안 이러한 변수를 레지스터에 유지할 수 있는 것은 간단하고 유용한 최적화이며 더 일반적인 유형의 펀닝 패턴을 사용하는 코드에 대한 지원을 배제하지 않을 것입니다( 컴파일러가 a를 해석하도록 함).float*.int*모든 등록부를 삭제할 필요가 있다고 판단할 수 있습니다.float값은 단순하고 간단합니다. 그러한 캐스트는 그러한 접근 방식이 성능에 악영향을 미칠 가능성이 없을 만큼 충분히 드물기 때문입니다.

  2. 이 표준이 일반적으로 주어진 플랫폼에 대해 좋은 품질의 구현을 만드는 것에 대해 불가지론적인 것을 고려할 때, 이 규칙은 구현이 유용하고 명백한 방식으로 유형 처벌을 사용하는 코드를 어기는 것을 허용하는 것으로 해석될 수 있습니다.우수한 품질의 구현이 그렇게 하는 것을 피해서는 안 된다는 것을 암시하지 않습니다.

이 기준서에서 다른 접근법에 비해 크게 열등하지 않은 현장형 처벌을 허용하는 실질적인 방법을 정의한다면, 정의된 방법 이외의 접근법은 합리적으로 권장되지 않는 것으로 간주될 수 있습니다.Standard가 정의한 수단이 존재하지 않는 경우, 좋은 성능을 얻기 위해 유형 처벌이 필요한 플랫폼의 품질 구현은 Standard의 요구 여부와 상관없이 플랫폼의 공통 패턴을 효율적으로 지원하도록 노력해야 합니다.

불행하게도, 이 표준이 요구하는 것이 무엇인지에 대한 명확성이 부족하여 일부 사람들이 대체품이 존재하지 않는 감가상각 시공으로 간주하는 상황이 발생했습니다.두 개의 원시 유형을 포함하는 완전한 결합 유형 정의의 존재는 한 유형의 포인터를 통한 접근이 다른 유형에 대한 접근으로 간주되어야 한다는 것을 나타내는 것으로 해석됩니다. 이는 정의되지 않은 동작(Undefined Behavior) 없이도 인플레이스 유형 펀닝에 의존하는 프로그램을 조정할 수 있습니다.현재 표준에 주어진 다른 실용적인 방법을 달성할 수 있습니다.불행하게도, 이러한 해석은 또한 무해한 경우의 99%에서 많은 최적화를 제한할 수 있으므로, 기존 코드를 효율적으로 실행하기 위해 표준을 해석하는 컴파일러에게는 불가능합니다.

규칙이 정확하게 지정되었는지 여부에 관해서는 그것이 무엇을 의미하는지에 달려 있습니다.여러 개의 합리적인 해석이 가능하지만 이를 결합하면 다소 불합리한 결과가 나옵니다.

PS-- 포인터 비교와 관련된 규칙에 대한 유일한 해석.memcpy에일리어싱 규칙에서 "객체"라는 용어에 그 의미와 다른 의미를 부여하지 않고는 어떤 할당된 영역도 단일 종류 이상의 객체를 보유하는 데 사용될 수 없음을 시사할 것입니다.어떤 종류의 코드는 이러한 제한을 준수할 수 있지만, 프로그램이 과도한 수의 malloc/free call 없이 스토리지를 재활용하기 위해 자체 메모리 관리 로직을 사용하는 것은 불가능합니다.이 표준의 저자들은 프로그래머들이 큰 영역을 만들고 더 작은 혼합형 청크로 분할하는 데 구현이 필요하지 않다고 말할 의도였을 수도 있지만, 범용 구현이 실패할 것이라는 뜻은 아닙니다.

이 표준에서는 구성원 유형의 l 값을 사용하여 구조물 또는 조합의 저장된 값에 접근할 수 없습니다.예제에서는 조합의 유형이 아닌 l 값을 사용하거나 해당 조합을 포함하는 모든 유형을 사용하여 조합의 저장된 값에 액세스하므로 해당 기준에서만 동작이 정의되지 않습니다.

까다로운 한 가지는 엄격한 표준 판독 하에서 심지어 다음과 같이 간단한 것도

int main(void)
{
  struct { int x; } foo;
  foo.x = 1;
  return 0;
}

N1570 6.5p7 합니다를 합니다.foo.x유형의 l 값입니다.int, 유형의 개체의 저장된 값에 액세스하는 데 사용됩니다.struct foo, 활자를int해당 섹션의 조건을 충족하지 않습니다.

이 표준이 원격으로 유용할 수 있는 유일한 방법은 다른 l값에서 파생된 l값을 포함하는 경우 N1570 6.5p7에 대한 예외가 필요하다는 것을 인식하는 경우뿐입니다.컴파일러가 그러한 파생을 인식할 수 있거나 인식해야 하는 경우를 기술하고, N1570 6.5p7은 기능 또는 루프의 특정 실행 내에서 둘 이상의 유형을 사용하여 스토리지에 액세스하는 경우에만 적용된다고 명시한다면,"유효한 유형"이라는 개념에 대한 필요성을 포함하여 많은 복잡성을 제거했을 것입니다.

불행하게도 일부 컴파일러들은 다음과 같은 명백한 경우에도 l값과 포인터의 도출을 무시하는 것을 스스로 감수했습니다.

s1 *p1 = &unionArr[i].v1;
p1->x ++;

컴파일러가 다음과 같은 연관성을 인식하지 못하는 것이 합리적일 수 있습니다.p1그리고.unionArr[i].v1관련된 다른 행동이 있다면unionArr[i]p1의 생성과 사용을 분리하였으나, gcc나 clang 모두 포인터의 사용이 조합원의 주소를 사용하는 동작을 곧바로 따르는 단순한 경우에도 그러한 연관성을 지속적으로 인식할 수 없습니다.

다시 말하지만, 이 표준은 컴파일러가 문자 유형이 아닌 한 파생 l 값의 사용을 인식하도록 요구하지 않기 때문에 gcc와 clang의 동작이 일치하지 않습니다.반면에, 그들이 준수하는 유일한 이유는 표준의 결함 때문입니다. 이는 너무 말도 안 되는 것이어서 아무도 표준이 실제로 무엇을 하는지 말하는 것으로 읽지 않습니다.

언급URL : https://stackoverflow.com/questions/38798140/is-the-strict-aliasing-rule-incorrectly-specified

반응형