source

관련이 없는 포인터의 동등한 비교가 참으로 평가될 수 있습니까?

manycodes 2023. 6. 30. 22:29
반응형

관련이 없는 포인터의 동등한 비교가 참으로 평가될 수 있습니까?

C 표준의 섹션 6.5.9는 다음과 관련하여==그리고.!=연산자는 다음을 명시합니다.

2 다음 중 하나가 유지되어야 합니다.

  • 두 피연산자 모두 산술 유형을 가집니다.
  • 두 피연산자 모두 호환 가능한 형식의 한정판 또는 비한정판에 대한 포인터입니다.
  • 하나의 피연산자는 객체 형식에 대한 포인터이고, 다른 하나는 void의 한정된 버전 또는 비한정 버전에 대한 포인터입니다.
  • 한 피연산자는 포인터이고 다른 피연산자는 널 포인터 상수입니다.

...

6포인터가 모두 null 포인터인 경우에만 동일하게 비교됩니다. 둘 다 동일한 객체에 대한 포인터입니다. (객체에 대한 포인터와 시작 부분에 있는 하위 객체를 포함합니다.) 또는 함수, 둘 다 동일한 배열 객체의 마지막 요소에 대한 포인터입니다.또는 하나는 하나의 배열 개체의 끝을 지나 하나의 배열 개체에 대한 포인터이고 다른 하나는 주소 109)공간에서 첫 번째 배열 개체를 바로 따르는 다른 배열 개체의 시작에 대한 포인터입니다.

7 이러한 연산자의 목적상 배열의 요소가 아닌 객체에 대한 포인터는 객체의 유형을 요소 유형으로 하는 길이가 1인 배열의 첫 번째 요소에 대한 포인터와 동일하게 동작합니다.

각주 109:

109) 두의 객체는 더 큰 배열의 인접 요소이거나 구조물의 인접 요소이며 그 사이에 패딩이 없기 때문에 또는 관련이 없음에도 불구하고 구현체가 배치하기로 선택했기 때문메모리에서 인접할 수 있습니다.잘못된 이전 포인터 작업(예: 어레이 경계 외부 액세스)에서 정의되지 않은 동작이 생성된 경우 후속 비교에서도 정의되지 않은 동작이 생성됩니다.

이는 다음 작업을 수행할 수 있음을 나타냅니다.

int a;
int b;
printf("a precedes b: %d\n", (&a + 1) == &b);
printf("b precedes a: %d\n", (&b + 1) == &a);

배열의 끝(이 경우 크기가 1인 단일 개체로 처리됨)을 무시하지 않고 하나의 요소를 사용하므로 이는 합법적이어야 합니다.더 중요한 것은 이 두 개의 문 중 하나가 출력을 위해 필요하다는 것입니다.1메모리에서 한 변수가 다른 변수를 바로 따르는 경우.

하지만, 테스트는 이것을 해결하지 못한 것처럼 보였습니다.다음 테스트 프로그램이 주어집니다.

#include <stdio.h>

struct s {
    int a;
    int b;
};

int main()
{
    int a;
    int b;
    int *x = &a;
    int *y = &b;

    printf("sizeof(int)=%zu\n", sizeof(int));
    printf("&a=%p\n", (void *)&a);
    printf("&b=%p\n", (void *)&b);
    printf("x=%p\n", (void *)x);
    printf("y=%p\n", (void *)y);

    printf("addr: a precedes b: %d\n", ((&a)+1) == &b);
    printf("addr: b precedes a: %d\n", &a == ((&b)+1));
    printf("pntr: a precedes b: %d\n", (x+1) == y);
    printf("pntr: b precedes a: %d\n", x == (y+1));

    printf("  x=%p,   &a=%p\n", (void *)(x), (void *)(&a));
    printf("y+1=%p, &b+1=%p\n", (void *)(y+1), (void *)(&b+1));

    struct s s1;
    x=&s1.a;
    y=&s1.b;
    printf("addr: s.a precedes s.b: %d\n", ((&s1.a)+1) == &s1.b);
    printf("pntr: s.a precedes s.b: %d\n", (x+1) == y);
    return 0;
}

컴파일러는 gcc 4.8.5, 시스템은 CentOS 7.2 x64입니다.

와 함께-O0다음과 같은 출력이 표시됩니다.

sizeof(int)=4
&a=0x7ffe9498183c
&b=0x7ffe94981838
x=0x7ffe9498183c
y=0x7ffe94981838
addr: a precedes b: 0
addr: b precedes a: 0
pntr: a precedes b: 0
pntr: b precedes a: 1
  x=0x7ffe9498183c,   &a=0x7ffe9498183c
y+1=0x7ffe9498183c, &b+1=0x7ffe9498183c
addr: s.a precedes s.b: 1

우리는 여기서 볼 수 있습니다.int이고 그 는 4바이트입니다.a는 의주바 4이초니다습과했의 를 4바이트 입니다.b 그 그에밖x의 주소를 보유하고 있습니다.a하는 동안에y의 주소를 보유하고 있습니다.b하지만 그 비교는&a == ((&b)+1)는 false로 평가됩니다.(x+1) == y참으로 평가됩니다.비교되는 주소가 동일하기 때문에 두 가지 모두 사실일 것으로 예상합니다.

와 함께-O1이해합니다.

sizeof(int)=4
&a=0x7ffca96e30ec
&b=0x7ffca96e30e8
x=0x7ffca96e30ec
y=0x7ffca96e30e8
addr: a precedes b: 0
addr: b precedes a: 0
pntr: a precedes b: 0
pntr: b precedes a: 0
  x=0x7ffca96e30ec,   &a=0x7ffca96e30ec
y+1=0x7ffca96e30ec, &b+1=0x7ffca96e30ec
addr: s.a precedes s.b: 1
pntr: s.a precedes s.b: 1

이제 비교되는 주소가 동일한 것처럼 보이지만(이전과 마찬가지로) 두 비교 모두 거짓으로 평가됩니다.

이것은 정의되지 않은 행동을 가리키는 것처럼 보이지만, 제가 위의 구절을 읽는 방법에 따르면 이것은 허용되어야 할 것 같습니다.

또한 를 한또, 같유형인객주접체소비다같다로 비교하는 할 수 .struct모든 경우에 예상 결과를 출력합니다.

제가 여기서 허용되는 것(이것이 UB라는 의미)에 대해 잘못 읽은 것이 있습니까, 아니면 이 경우 gcc 버전이 적합하지 않습니까?

관련이 없는 포인터의 동등한 비교가 참으로 평가될 수 있습니까?

네, 하지만...

int a;
int b;
printf("a precedes b: %d\n", (&a + 1) == &b);
printf("b precedes a: %d\n", (&b + 1) == &a);

C 표준에 대한 나의 해석에 따르면 세 가지 가능성이 있습니다.

  • b의 바로 앞에 있는 a
  • b는 a의 바로 앞에 있습니다.
  • a나 b는 다른 것의 바로 앞에 있지 않습니다(그들 사이에 틈이나 다른 물체가 있을 수 있습니다).

저는 얼마 전에 이것을 가지고 놀았고 GCC가 유효하지 않은 최적화를 수행하고 있다고 결론 내렸습니다.==포인터 연산자는 주소가 같을 때도 거짓을 산출하게 하여 버그 보고서를 제출했습니다.

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63611

해당 버그는 다른 보고서의 복제로 닫혔습니다.

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61502

이러한 버그 보고서에 응답한 GCC 유지 관리자들은 두 개체의 인접 관계가 일관될 필요가 없으며 주소의 비교가 프로그램의 동일한 실행 내에서 인접 관계에 있는지 여부를 보여줄 수 있다고 생각하는 것 같습니다.두 번째 Bugzilla 티켓에 대한 제 의견을 보시면 아시겠지만, 저는 강하게 반대합니다.제 생각에는, 일관된 행동 없이는.==연산자, 인접한 물체에 대한 표준의 요구 사항은 의미가 없으며, 저는 우리가 그 단어들이 단순히 장식적인 것이 아니라고 가정해야 한다고 생각합니다.

다음은 간단한 테스트 프로그램입니다.

#include <stdio.h>
int main(void) {
    int x;
    int y;
    printf("&x = %p\n&y = %p\n", (void*)&x, (void*)&y);
    if (&y == &x + 1) {
        puts("y immediately follows x");
    }
    else if (&x == &y + 1) {
        puts("x immediately follows y");
    }
    else {
        puts("x and y are not adjacent");
    }
}

6으로 컴파일하면 GCC 6.2.의 인쇄된 .x그리고.y모든 최적화 수준에서 정확히 4바이트 차이가 나지만, 저는 이해합니다.y immediately follows x에 한-O0-O1,-O2,그리고.-O3알겠습니다x and y are not adjacent저는 이것이 잘못된 행동이라고 생각하지만, 분명히, 그것은 고쳐지지 않을 것입니다.

1은 하며, 내생에 clang 3.8.1을 보여줍니다.x immediately follows y모든 최적화 수준에서.Clang은 이전에 이 문제에 대해 문제가 있었습니다. 제가 보고했습니다.

https://bugs.llvm.org/show_bug.cgi?id=21327

그리고 그것은 수정되었습니다.

저는 일관되게 동작하는 인접 객체의 주소 비교에 의존하지 않는 것을 제안합니다.

연산자 관계연산자▁(<,<=,>,>=) 이 없는 포인터는 않은 연산자등호 연산자)는 정의되지 않았습니다.==,!= 있게 행동하도록 라는 의미입니다.

int a;
int b;
printf("a precedes b: %d\n", (&a + 1) == &b);
printf("b precedes a: %d\n", (&b + 1) == &a);

완벽하게 잘 정의된 코드이지만, 판단보다는 운에 의한 것일 수 있습니다.

스칼라의 주소를 가져와서 해당 주소를 지나 포인터를 설정할 수 있습니다.그렇게&a + 1 유하지만효,만▁is유지,.&a + 2같은 포인터비교있습니다.==그리고.!=포인터 산술은 배열 내에서만 사용할 수 있습니다.

당의주주가소의 a그리고.b메모리에 저장되는 방법에 대한 모든 정보를 알려줍니다.확실히 말하자면, 당신은 "도달"할 수 없습니다.b에 대포연로으의 에 대한 .a.

에 대해서는

struct s {
    int a;
    int b;
};

은 표은다음보다니장의 합니다.struct는 의주와동다니합의 .a하지만 임의의 양의 패딩이 허용됩니다.a그리고.b다시 말하지만, 당신은 당신의 주소에 연락할 수 없습니다.b의 주 에 관 포 연 으도로산의 주소에 .a.

관련이 없는 포인터의 동등한 비교가 참으로 평가될 수 있습니까?

예. C는 이것이 참인 경우를 지정합니다.

두 포인터는 다음과 같은 경우에만 동일하게 비교됩니다.또는 하나는 하나의 배열 개체의 끝을 지나 하나의 배열 개체에 대한 포인터이고 다른 하나는 주소 공간에서 첫 번째 배열 개체를 바로 따르는 다른 배열 개체의 시작에 대한 포인터입니다.C11dr §6.5.96

명확하게 하기: 코드의 인접 변수는 메모리에서 인접할 필요는 없지만, 가능할 수 있습니다.


아래 코드는 그것이 가능하다는 것을 보여줍니다.의 메모리 덤프를 사용합니다.int*의 종의관더하에 ."%p"그리고.(void*).

하지만 OP의 코드와 출력은 이것을 반영하지 않습니다.위 사양의 "if and only if" 부분을 고려할 때, OP의 컴파일은 비준수입니다.메모리 변수에 인접p,q&p+1 == &q또는&p == &q+1사실이어야 합니다.

객체의 유형이 다를 경우 의견 없음 - OP는 IAC를 요청하지 않습니다.


void print_int_ptr(const char *prefix, int *p) {
  printf("%s %p", prefix, (void *) p);
  union {
    int *ip;
    unsigned char uc[sizeof (int*)];
  } u = {p};
  for (size_t i=0; i< sizeof u; i++) {
    printf(" %02X", u.uc[i]);
  }
  printf("\n");
}

int main(void) {
  int b = rand();
  int a = rand();
  printf("sizeof(int) = %zu\n", sizeof a);
  print_int_ptr("&a     =", &a);
  print_int_ptr("&a + 1 =", &a + 1);
  print_int_ptr("&b     =", &b);
  print_int_ptr("&b + 1 =", &b + 1);
  printf("&a + 1 == &b: %d\n", &a + 1 == &b);
  printf("&a == &b + 1: %d\n", &a == &b + 1);
  return a + b;
}

산출량

sizeof(int) = 4
&a     = 0x28cc28 28 CC 28 00
&a + 1 = 0x28cc2c 2C CC 28 00  <-- same bit pattern
&b     = 0x28cc2c 2C CC 28 00  <-- same bit pattern
&b + 1 = 0x28cc30 30 CC 28 00
&a + 1 == &b: 1                <-- compare equal
&a == &b + 1: 0

이 표준의 저자들은 이 표준을 "언어 변호사 증명"으로 만들려고 노력하지 않았고, 결과적으로, 이 표준은 다소 모호합니다.이러한 모호성은 컴파일러 작가들이 최소 놀라움의 원칙을 지지하기 위해 성실한 노력을 할 때 일반적으로 문제가 되지 않을 것입니다. 왜냐하면 명백한 비놀라운 행동이 있고 다른 행동은 놀라운 결과를 초래할 것이기 때문입니다.다른 한편으로는 기존 코드와 호환되는지 여부보다 표준을 읽는 동안 최적화가 정당화될 수 있는지 여부에 더 관심이 있는 컴파일러 작성자들이 비호환성을 정당화할 수 있는 흥미로운 기회를 찾을 수 있다는 것을 의미합니다.

이 기준서는 포인터의 표현이 기본 물리적 아키텍처와 관련이 있다고 요구하지 않습니다.시스템이 각 포인터를 핸들과 오프셋의 조합으로 표시하는 것은 완전히 합법적일 것입니다.이러한 방식으로 포인터를 표현한 시스템은 물리적 스토리지에서 적합하다고 판단될 때 자유롭게 객체를 이동할 수 있습니다.이러한 시스템에서 객체 #57의 첫 번째 바이트는 한 순간에 객체 #23의 마지막 바이트 바로 뒤에 올 수 있지만, 다른 순간에는 완전히 관련이 없는 위치에 있을 수 있습니다.나는 표준에서 그러한 구현이 객체 #23에 대한 "과거" 포인터를 두 객체가 인접할 때 객체 #57에 대한 포인터와 동일하고 그렇지 않을 때 동일하지 않다고 보고하는 것을 금지하는 것을 아무것도 보지 않습니다.

또한, as-if 규칙에 따르면, 객체를 그러한 방식으로 이동시키고 결과적으로 특이한 동등 연산자를 갖는 것이 정당화될 수 있는 구현은 저장소에서 객체를 물리적으로 이동하든 그렇지 않든 상관없이 특이한 동등 연산자를 가질 수 있습니다.

그러나 구현이 포인터가 RAM에 저장되는 방법을 지정하고 이러한 정의가 위에서 설명한 동작과 일치하지 않을 경우 구현은 해당 사양과 일치하는 방식으로 동일 연산자를 구현해야 합니다.특이한 등식 연산자를 사용하려는 컴파일러는 이러한 동작과 일치하지 않는 포인터 저장소 형식을 지정하지 않아야 합니다.

또한 이 표준은 정의된 값을 가진 두 포인터가 동일한 표현을 갖는 경우 코드가 동일하게 비교해야 한다는 것을 의미하는 것으로 보입니다.문자 유형을 사용하여 개체를 읽은 다음 문자 유형 값의 동일한 시퀀스를 다른 개체에 쓰면 원래 개체와 동등한 개체가 생성됩니다. 이러한 동등성은 언어의 기본적인 특징입니다.한다면p개체를 "이고, 하의바객체 "로지나감니" "입다터포인" "나를니입다""입니다.q개체에 이며, 의 표현은 다개체대포한며이인, 해표에복니다됩사이현당으로 됩니다.p2그리고.q2그럼 각각p1를 와동하 비야합 니다해와 하게 비교해야 합니다.p그리고.q2q 문된 표인 현경우의 표현인 p그리고.q동등하다, 그것은 그것을 의미합니다.q2와 동일한 문자 유형 값 시퀀스로 작성되었습니다.p1이는 네 개의 포인터가 모두 같아야 한다는 것을 의미합니다.

결과적으로, 컴파일러가 바이트 수준 표현을 관찰할 수 있는 코드에 절대 노출되지 않는 포인터에 대해 특이한 동등성 의미론을 갖는 것은 허용될 수 있지만, 그러한 행동 라이센스는 노출된 포인터로 확장되지 않을 것입니다.구현체가 컴파일러들이 한 객체의 끝과 그와 같은 비교를 통해서만 관찰할 수 있는 다른 객체의 시작에 대한 포인터가 주어졌을 때 개별 비교를 임의로 보고하도록 초대하는 지시나 설정을 정의한다면,구현은 포인터 표현이 관찰되는 경우 적합성에 대해 걱정할 필요가 없습니다.그렇지 않으면, 적합한 구현이 기발한 비교 의미론을 가질 수 있는 경우에도, 한 개체의 끝을 바로 지난 포인터가 자연스럽게 포인터에서 다음 개체의 시작까지 다른 표현을 가지지 않는 한 초대되지 않는 한 품질 구현이 그렇게 해야 한다는 것을 의미하지 않습니다.

언급URL : https://stackoverflow.com/questions/45966762/can-an-equality-comparison-of-unrelated-pointers-evaluate-to-true

반응형