programing

C에서 포인터 비교는 어떻게 이루어집니까?같은 배열을 가리키지 않는 포인터를 비교해도 괜찮습니까?

starjava 2023. 10. 14. 09:26
반응형

C에서 포인터 비교는 어떻게 이루어집니까?같은 배열을 가리키지 않는 포인터를 비교해도 괜찮습니까?

K&R(The C Programming Language 2nd Edition) 5장에서 나는 다음과 같이 읽습니다.

첫째, 어떤 상황에서는 포인터들이 비교될 수 있습니다. 만약p그리고.q음,다와 를 가리킵니다.==,!=,<,>=이 제대로.

같은 배열을 가리키는 포인터만 비교할 수 있음을 의미하는 것 같습니다.

하지만 내가 이 코드를 시도했을 때

    char t = 't';
    char *pt = &t;
    char x = 'x';
    char *px = &x;

    printf("%d\n", pt > px);

1가 화면에 인쇄됩니다.

저는 했습니다, , 했습니다가 pt그리고.px(적어도 제가 알기로는) 같은 배열을 가리키고 있지 않습니다.

.pt > px 모두 의 st다의 보다 더 .x 왜죠? .pt > px사실입니까?

저는 malloc을 들여오면 더 혼란스러워집니다.8.7장의 K&R에도 다음과 같이 기술되어 있습니다.

sbrk의미 있게 비교할 수 있습니다.배열 내에서만 포인터 비교를 허용하는 표준에서는 이를 보장하지 않습니다. 이 nmalloc는 일반적인 포인터 비교가 의미 있는 기계들 사이에서만 휴대 가능합니다.

저는 힙에 할당된 공간을 가리키는 포인터와 스택 변수를 가리키는 포인터를 비교하는 데 아무런 문제가 없었습니다.

를 들어, 코드는 했습니다, 했습니다.1 중:중:

    char t = 't';
    char *pt = &t;
    char *px = malloc(10);
    strcpy(px, pt);
    printf("%d\n", pt > px);

컴파일러에 대한 제 실험을 바탕으로, 저는 어떤 포인터라도 그 포인터들이 개별적으로 가리키는 위치에 상관없이 다른 어떤 포인터와도 비교될 수 있다고 생각하게 되었습니다.또한 포인터가 저장한 메모리 주소를 사용하기 때문에 개별적으로 어디를 가리키든 상관없이 포인터 산술은 괜찮다고 생각합니다.

그래도 K&R에서 무엇을 읽고 있는지 헷갈립니다.

제가 묻는 이유는 교수님 때문입니다.실제로 시험문제로 삼았습니다.그는 다음과 같은 암호를 제시했습니다.

struct A {
    char *p0;
    char *p1;
};

int main(int argc, char **argv) {
    char a = 0;
    char *b = "W";
    char c[] = [ 'L', 'O', 'L', 0 ];

   struct A p[3];
    p[0].p0 = &a;
    p[1].p0 = b;
    p[2].p0 = c;

   for(int i = 0; i < 3; i++) {
        p[i].p1 = malloc(10);
        strcpy(p[i].p1, p[i].p0);
    }
}

다음 항목에 대해 평가하는 항목으로 평가합니까?

  1. p[0].p0 < p[0].p1
  2. p[1].p0 < p[1].p1
  3. p[2].p0 < p[2].p1

은 입니다.0,1,그리고.0.

(교수님께서 시험에 Ubuntu Linux 16.04 64비트 버전 프로그래밍 환경에 대한 질문이라는 고지 사항을 포함하고 있습니다.)

(편집자 주: SO가 더 많은 태그를 허용한다면, 그 마지막 부분은 , , 그리고 아마도 를 보장할 것입니다.질문/클래스의 요점이 휴대용 C가 아닌 하위 OS 구현 세부사항일 경우)

C11 표준에 따르면 관계 연산자는<,<=,>,그리고.>=동일한 배열 또는 구조 개체의 요소에 대한 포인터에서만 사용할 수 있습니다. 6.8p5:다 6.5.8p5션에 .

두 포인터를 비교할 때, 그 결과는 가리키는 객체의 주소 공간에서의 상대적인 위치에 따라 달라집니다.객체 유형에 대한 두 포인터가 둘 다 동일한 객체를 가리키거나 둘 다 동일한 배열 객체의 마지막 요소를 지나 하나를 가리키면 동일한 값을 비교합니다.가리킨 개체가 동일한 집계 개체의 멤버인 경우 나중에 선언된 구조 구성원에 대한 포인터는 구조 이전에 선언된 구성원에 대한 포인터보다 크고, 첨자 값이 큰 배열 요소에 대한 포인터는 하위 첨자 값이 작은 동일 배열의 요소에 대한 포인터보다 큽니다.동일한 연합 개체의 구성원에 대한 모든 포인터가 동일하게 비교됩니다.식 P가 배열 개체의 요소를 가리키고 식 Q가 동일한 배열 개체의 마지막 요소를 가리키면 포인터 식 Q+1이 P보다 크게 비교됩니다.다른 모든 경우에는 동작이 정의되지 않습니다.

이 요구 사항을 충족하지 못하는 비교는 정의되지 않은 동작을 불러옵니다. 이는 반복 가능한 결과에 의존할 수 없다는 것을 의미합니다.

특정한 경우 두 로컬 변수의 주소와 로컬 주소와 동적 주소 사이의 비교 모두에서 연산이 "작동"하는 것처럼 보였지만 코드를 관련이 없어 보이는 변경을 수행하거나 다른 최적화 설정으로 동일한 코드를 컴파일하는 경우에도 결과가 변경될 수 있습니다.정의되지 않은 동작의 경우, 코드가 충돌하거나 오류가 발생할 수 있다고 해서 그렇게 되는 것은 아닙니다.

예를 들어, 8086 리얼 모드에서 실행되는 x86 프로세서는 16비트 세그먼트와 16비트 오프셋을 사용하여 20비트 주소를 구축하는 세그먼트 메모리 모델을 가지고 있습니다.따라서 이 경우 주소가 정확히 정수로 변환되지는 않습니다.

==그리고.!=그러나 이 제한을 두지 마십시오.호환되는 유형 또는 NULL 포인터에 대한 임의의 두 포인터 간에 사용할 수 있습니다.그래서 사용.==아니면!=두 예시 모두에서 유효한 C 코드를 생성할 것입니다.

만을 하더라도,==그리고.!=예상치 못했지만 여전히 명확한 결과를 얻을 수 있습니다.관련이 없는 포인터의 동등성 비교를 참으로 평가할 수 있습니까?를 참조하십시오.이에 대한 자세한 사항은.

교수님이 출제한 시험 문제와 관련하여, 다음과 같은 몇 가지 결함 있는 가정을 제시하고 있습니다.

  • 주소와 정수 값 사이에 1 대 1 대응 관계가 있는 플랫 메모리 모델이 존재합니다.
  • 변환된 포인터 값이 정수 유형 안에 들어 있도록 합니다.
  • 구현은 비교를 수행할 때 정의되지 않은 동작에 의해 주어진 자유를 이용하지 않고 포인터를 정수로 취급할 뿐입니다.
  • 스택이 사용되고 로컬 변수가 저장됩니다.
  • 해당 힙은 할당된 메모리를 꺼내는 데 사용됩니다.
  • 스택(따라서 로컬 변수)이 힙(따라서 할당된 개체)보다 높은 주소에 나타납니다.
  • 해당 문자열 상수는 힙보다 낮은 주소에 나타납니다.

이 코드를 아키텍처 및/또는 이러한 가정을 충족하지 않는 컴파일러에서 실행하면 매우 다른 결과를 얻을 수 있습니다.

두 모두 를 할 때 되지 않은 .strcpy에 따라)는 ()는 null 로를 합니다

포인터를 같은 유형의 서로 다른 두 어레이와 비교할 때 가장 큰 문제는 어레이 자체를 특정 상대 위치에 배치할 필요가 없다는 것입니다. 즉, 하나는 다른 어레이의 앞뒤로 배치될 수 있습니다.

우선 pt와 px가 (적어도 내가 이해하는 한) 동일한 배열을 가리키고 있지 않기 때문에 정의되지 않거나 어떤 유형이나 오류가 생길 것이라고 생각했습니다.

아니요, 결과는 구현 및 기타 예측 불가능한 요소에 따라 달라집니다.

또한 pt>px인데, 두 포인터 모두 스택에 저장된 변수를 가리키며 스택이 아래로 성장하기 때문에 t의 메모리 주소가 x의 메모리 주소보다 더 커?이것이 pt>px가 참인 이유입니까?

반드시 스택이 있는 것은 아닙니다.그것이 존재할 때, 그것은 성장할 필요가 없습니다.성장할 수도 있어요.이상한 방법으로 연속적이지 않을 수도 있습니다.

또한 포인터가 저장한 메모리 주소를 사용하기 때문에 개별적으로 어디를 가리키든 상관없이 포인터 산술은 괜찮다고 생각합니다.

관계 연산자(즉, 사용 중인 비교 연산자)에 대해 설명하는 85페이지의 C 사양 §6.5.8을 살펴보겠습니다.이는 직접적인 경우에는 적용되지 않습니다.!=아니면==비교.

두 포인터를 비교할 때, 결과는 가리키는 객체의 주소 공간에서의 상대적인 위치에 따라 달라집니다. ...가리킨 개체가 동일한 집계 개체의 멤버인 경우...첨자 값이 큰 배열 요소에 대한 포인터가 하위첨자 값이 작은 동일 배열 요소에 대한 포인터보다 큽니다.

다른 모든 경우에는 동작이 정의되지 않습니다.

마지막 문장이 중요합니다.공간을 절약하기 위해 관련이 없는 몇 가지 사례를 줄였지만, 우리에게 중요한 한 가지 사례가 있습니다. 두 개의 어레이가 동일한 구조1/집적 개체의 일부가 아닌 두 개의 어레이와 포인터를 비교하는 것입니다.정의되지 않은 동작입니다.

당신의 컴파일러가 방금 포인터를 수치적으로 비교하는 일종의 CMP(compare) 기계 명령을 삽입했는데, 당신은 여기서 운이 좋았지만, UB는 꽤 위험한 짐승입니다.말 그대로 모든 일이 일어날 수 있습니다. 컴파일러는 눈에 보이는 부작용을 포함한 전체 기능을 최적화할 수 있습니다.코로 된 악마들을 낳을 수도 있어요

동일한 구조에 속하는 두 개의 다른 배열에 대한 1포인터는 두 배열이 동일한 집합 개체(구조)의 일부인 절에 해당하므로 비교할 수 있습니다.

그럼 뭐라고 물었더니.

p[0].p0 < p[0].p1
p[1].p0 < p[1].p1
p[2].p0 < p[2].p1

평가합니다.정답은 0, 1, 0 입니다.

이러한 질문은 다음과 같이 줄어듭니다.

  1. 스택 위쪽 또는 아래쪽에 힙이 있습니까?
  2. 힙은 프로그램의 문자열 리터럴 섹션 위 또는 아래에 있습니다.
  3. [1]과 같은

그리고 세 가지 모두에 대한 답은 "구현 정의"입니다.교수님의 질문은 거짓입니다. 그들은 기존의 유닉스 레이아웃에 기반을 두고 있습니다.

<empty>
text
rodata
rwdata
bss
< empty, used for heap >
...
stack
kernel

그러나 몇몇 현대적인 유니크들(그리고 대안적인 시스템들)은 그러한 전통들에 부합하지 않습니다.문제 앞에 "1992년 현재"를 붙이지 않는 한, 반드시 평가에서 -1을 부여해야 합니다.

거의 모든 원격 현대 플랫폼에서 포인터와 정수는 동형 순서 관계를 가지며, 분리된 객체에 대한 포인터는 인터리브되지 않습니다.대부분의 컴파일러는 최적화가 비활성화된 경우 이 순서를 프로그래머에게 노출시키지만, 이 표준은 이러한 순서를 가진 플랫폼과 그렇지 않은 플랫폼을 구분하지 않으며, 구현이 이러한 순서를 정의하는 플랫폼에서도 프로그래머에게 노출시킬 필요가 없습니다.따라서 일부 컴파일러 작성자는 코드가 다른 개체에 대한 포인터에서 사용 관계 연산자를 비교하지 않는다는 가정에 따라 다양한 유형의 최적화 및 "최적화"를 수행합니다.

발표된 이론적 근거에 따르면, 표준의 저자들은 구현이 표준이 "정의되지 않은 행동"(즉, 표준이 아무런 요구사항을 부과하지 않는 경우)이라고 특징짓는 상황에서 어떻게 행동할 것인지를 지정함으로써 언어를 확장할 의도였지만, 일부 컴파일러 작성자들은 오히려 언어를 확장할 것입니다.프로그램이 추가 비용 없이 플랫폼이 지원할 수 있는 행위를 유용하게 활용할 수 있도록 허용하는 것이 아니라 프로그램이 표준이 규정하는 것 이상의 혜택을 누리지는 않을 것이라고 가정합니다.

저는 포인터 비교를 통해 이상한 행동을 하는 상업적으로 설계된 컴파일러에 대해 알지 못하지만, 컴파일러가 백엔드를 위해 비상업적인 LLVM으로 이동함에 따라 이전 컴파일러가 자신의 플랫폼을 위해 동작을 지정한 무의미한 코드를 처리할 가능성이 점점 높아지고 있습니다.이러한 행동은 관계형 연산자에 국한되지 않고 평등/불평등에도 영향을 미칠 수 있습니다.예를 들어, 한 객체에 대한 포인터와 바로 앞 객체에 대한 "과거" 포인터를 비교하는 것이 동일하다고 표준에서 명시하고 있지만, 프로그램이 이러한 비교를 수행할 경우 gcc와 LLVM 기반 컴파일러는 무의미한 코드를 생성하기 쉽습니다.

gcc와 clang에서 동등성 비교조차 무의미하게 동작하는 상황의 예로 다음을 생각해 보십시오.

extern int x[],y[];
int test(int i)
{
    int *p = y+i;
    y[0] = 4;
    if (p == x+10)
        *p = 1;
    return y[0];
}

둘 다 clang gcc 4 를입니다를 해도 합니다.x10개의 원소가 있습니다.y,다.i이고 0고입니다.p[0]1다의 되었습니다.한 번의 처럼 함수를 쓰는 이라고 생각합니다.*p = 1;되었습니다로 x[10] = 1;가 . 입니다를 해석한다면 의 코드는 입니다.*(x+10)*(y+i)는 , 에 합니다.x[10]다음과 같은 경우에만 정의될 것입니다.x 있었는데,그도 11고,에 영향을 미치는 입니다.y.

만약 컴파일러들이 표준에 의해 설명된 포인터 평등 시나리오와 함께 그 "창의적"을 얻을 수 있다면, 나는 표준이 요구사항을 부과하지 않는 경우에 그들이 훨씬 더 창의적이 되는 것을 자제할 것이라고 믿지 않을 것입니다.

간단합니다. 포인터를 비교하는 것은 개체의 메모리 위치가 선언한 순서와 동일한 것이 보장되지 않기 때문에 의미가 없습니다.배열은 예외입니다.&array[0]가 &array[1]보다 낮습니다.K&R이 지적한 내용입니다.실제로 구조 구성원 주소 또한 당신이 신고하는 순서대로 제 경험에 따라 결정됩니다.그것에 대한 보장은 없습니다.또 다른 예외는 포인터를 동등한 값으로 비교하는 경우입니다.한 포인터가 다른 포인터와 같으면 같은 개체를 가리킨다는 것을 알 수 있습니다.뭐든 간에.나쁜 시험 문제를 내게 묻는다면.Ubuntu Linux 16.04에 따라 64비트 버전 프로그래밍 환경에서 시험 문제를? 정말?

포인터는 컴퓨터의 다른 모든 것처럼 정수일 뿐입니다.다와 할 수 .<그리고.>프로그램이 중단되지 않고 결과를 생성할 수 있습니다.즉, 표준은 이러한 결과가 배열 비교 이외의 의미를 갖는다는 것을 보장하지 않습니다.

할당된 변수 스택 예제에서 컴파일러는 해당 변수를 레지스터 또는 스택 메모리 주소에 자유롭게 할당할 수 있으며 어떤 순서로든 선택할 수 있습니다.교와 같은 .<그리고.>따라서 컴파일러나 아키텍처 간에 일관성을 유지하지 못합니다.만,==그리고.!=포인터 동일성을 비교하는 것은 유효하고 유용한 작업입니다.

정말 자극적인 질문이군요!

이 스레드에서 응답과 댓글을 간단히 스캔해도 겉보기에는 단순하고 단순해 보이는 질문이 얼마나 감정적인지 알 수 있습니다.

놀랄 일이 아닙니다.

의심할 여지 없이 포인터개념과 사용에 대한 오해는 일반적인 프로그래밍의 심각한 실패의 주요 원인입니다.

이러한 현실에 대한 인식은 특별히 다루도록 설계된 언어의 보편성에서 쉽게 알 수 있으며, 포인터가 도입하는 문제를 완전히 방지하는 것이 좋습니다.C++ 및 C, Java 및 그 관계, Python 및 기타 스크립트의 파생 모델인 C++를 단지 더 두드러지고 널리 사용되는 것으로 생각하고 문제를 다루는 정도가 어느 정도 질서정연하다고 생각합니다.

따라서 근본적인 원리를 더 깊이 이해하는 것은 프로그래밍의 우수성을 열망하는 모든 개인, 특히 시스템 수준과 관련이 있어야 합니다.

나는 이것이 바로 당신의 선생님이 보여주려는 것이라고 생각합니다.

그리고 C의 특성상 이 탐험에 편리한 차량이 됩니다.어셈블리보다는 명확하지 않지만(아마도 더 쉽게 이해할 수 있을지도 모르지만), 실행 환경에 대한 더 깊은 추상화에 기반을 둔 언어보다는 훨씬 더 명확합니다.

C는 프로그래머의 의도를 기계가 이해할 수 있는 명령어로 결정론적으로 쉽게 번역할 수 있도록 설계된 시스템 수준 언어입니다.상위 등급으로 분류되지만, 실제로는 '중간' 범주에 속하지만, 그러한 분류가 존재하지 않기 때문에 '시스템' 지정만으로도 충분합니다.

이러한 특성은 장치 드라이버, 운영 체제 코드 및 내장형 구현을 위한 언어로 만드는 데 크게 기여합니다.또한 최적의 효율성이 무엇보다 중요한 애플리케이션에서는 마땅히 선호되어야 할 대안이 됩니다. 여기서 이는 생존과 소멸의 차이를 의미하므로 사치품이 아닌 필수품입니다.그러한 경우, 휴대성의 매력적인 편리성은 모든 매력을 잃고, 최소 공통 분모의 부족한 성능을 선택하는 것은 생각할 수 없을 정도로 해로운 선택이 됩니다.

C와 일부 파생 제품을 아주 특별하게 만드는 것은 사용자가 원하는 대로 완전한 통제를 할 수 있게 해준다는 점입니다. 사용자가 원하는 대로 관련 책임지지 않아도 말이죠.그럼에도 불구하고 기계에서 나오는 가장 얇은 단열재 이상을 제공하는 경우는 없으며, 따라서 적절한 사용에는 포인터의 개념에 대한 정확이해가 필요합니다.

본질적으로, 당신의 질문에 대한 대답은 아주 간단하며, 당신의 의심을 확인시켜 주는 것으로 만족스럽게 달콤합니다.단,명세서의 모든 개념필요중요성부여하는 경우:

  • 포인터를 조사하고 비교하고 조작하는 행위는 항상 유효하고 반드시 유효한 반면, 결과에서 도출된 결론은 포함된 값의 유효성에 따라 달라지므로 그럴 필요는 없습니다.

전자는 항상 안전하고 잠재적으로 적절하지만, 후자는 그것이 안전으로 확립되었을 만 적절할 수 있습니다.놀랍게도 -- 어떤 사람들에게는 -- 그래서 후자의 타당성을 확립하는 은 전자에 달려있고 요구합니다.

물론 혼란의 일부는 포인터의 원리 안에 본질적으로 존재하는 재귀의 효과와 콘텐츠를 주소와 구별하는 데 제기되는 문제에서 발생합니다.

정확하게 추측하셨네요

저는 그들이 개별적으로 가리키는 위치에 상관없이 어떤 포인터라도 다른 포인터와 비교할 수 있다고 생각하게 되었습니다.또한 포인터가 저장한 메모리 주소를 사용하기 때문에 개별적으로 어디를 가리키든 상관없이 포인터 산술은 괜찮다고 생각합니다.

그리고 몇몇 기고자들은 포인터는 숫자에 불과하다고 단언했습니다.때로는 복잡한 숫자에 가까운 것도 있지만 여전히 숫자에 지나지 않습니다.

이 논쟁이 여기서 받아들여진 재미있는 신랄함은 프로그래밍보다는 인간의 본성에 대해 더 많이 드러내지만, 여전히 주목하고 정교할 가치가 있습니다.아마 우리는 나중에 그렇게 할 것입니다.

한 코멘트가 암시하기 시작한 것처럼, 이 모든 혼란과 경악은 무엇이 안전한지구별해야 할 필요성에서 비롯되지만, 그것은 지나치게 단순화된 것입니다.우리는 또한 무엇이 기능적이고 무엇이 신뢰할 수 있는지, 무엇이 실용적이고 무엇이 적절할 수 있는지, 더 나아가: 특정한 상황에서 적절한 것과 일반적인 의미에서 적절한 것을 구별해야 합니다.말할 것도 없이; 순응예의의 차이.

이를 위해서는 우선 포인터무엇인지 정확하게 이해해야 합니다.

  • 여러분은 이 개념에 대해 확고한 이해력을 보여주었고, 다른 사람들처럼 이러한 삽화들이 지나치게 단순하다고 여길 수도 있지만, 여기서 명백한 혼란의 정도는 명확한 설명을 요구합니다.

여러 사람들이 지적했듯이 포인터라는 용어는 단순히 인덱스인 것에 대한 특별한 이름일 뿐이므로 다른 숫자에 지나지 않습니다.

이것은 현대의 모든 주류 컴퓨터가 반드시 숫자만을 가지고 그리고 숫자에 대해서만 작동하는 이진 기계라는 사실을 고려할 때 이미 자명해야 합니다.양자 컴퓨팅은 그것을 바꿀 수 있지만, 그것은 매우 가능성이 낮으며, 아직 성숙하지 않았습니다.

엄밀히 말하면, 여러분이 언급했듯이, 포인터들은 더 정확한 주소입니다; 그것들을 집들의 '주소' 또는 거리의 플롯들과 연관시키는 보람 있는 비유를 자연스럽게 도입하는 명백한 통찰력입니다.

  • 플랫 메모리 모델에서: 전체 시스템 메모리는 하나의 선형 시퀀스로 구성됩니다. 도시의 모든 집은 동일한 도로에 놓여 있고 모든 집은 번호만으로 고유하게 식별됩니다.아주 간단합니다.

  • 세분화된 체계에서: 복합 주소가 필요하도록 번호가 매겨진 도로의 계층적 조직이 번호가 매겨진 주택의 계층적 조직 위에 도입됩니다.

    • 일부 구현은 여전히 더 복잡하며, 별개의 '도로'의 총합이 연속적인 시퀀스에 해당할 필요는 없지만, 그 어떤 것도 기본적인 것에 대해 아무것도 변하지 않습니다.
    • 우리는 반드시 모든 계층적 연결을 다시 평평한 조직으로 분해할 수 있습니다.조직이 복잡해질수록 우리는 그렇게 하기 위해 더 많은 후프를 헤쳐나가야 할 것입니다. 하지만 그것은 가능해야 합니다.실제로, 이것은 x86의 '리얼 모드'에도 적용됩니다.
    • 그렇지 않으면 시스템 수준에서 신뢰할 수 있는 실행을 요구하므로 위치에 대한 링크 매핑이 객관적이지 않을 것입니다.
      • 다중 주소는 단일 메모리 위치에 매핑되지 않아야 하며,
      • 단수 주소는 절대로 여러 메모리 위치에 매핑해서는 안 됩니다.

수수께끼를 아주 복잡한 엉킴으로 바꾸는 더 큰 반전으로 우리를 데려오는 것입니다.위에서 단순성과 명확성을 위해 포인터를 주소라고 제안하는 것은 편법이었습니다.물론 이것은 옳지 않습니다.포인터는 주소가 아니며 포인터는 주소에 대한 참조이며 주소포함되어 있습니다.봉투가 집을 가리켜 주는 것처럼.이것을 생각해 보면 개념에 포함된 재귀의 제안이 무엇을 의미하는지를 엿볼 수 있습니다.여전히, 우리는 단지 많은 단어만을 가지고 있고, 주소 등에 대한 참조 주소에 대해 이야기하는 것은 곧 대부분의 뇌를 잘못된 op-code 예외로 막히게 합니다.그리고 대부분의 경우, 의도는 맥락으로부터 쉽게 얻어지기 때문에, 다시 거리로 돌아가도록 합시다.

우리의 상상의 도시에 있는 우체부들은 우리가 '진짜' 세계에서 발견하는 우체부들과 매우 비슷합니다.잘못된 주소에 대해 이야기하거나 문의할 때 뇌졸중으로 고생하는 사람은 아무도 없지만, 그 정보에 대해 조치를 취하도록 요청할 때는 모든 사람이 주저할 것입니다.

우리의 특이한 거리에 집이 20채 밖에 없다고 가정해 보세요.또한 잘못 판단했거나 난독증에 걸린 사람들이 71번에게 편지를 보낸 것처럼 생각해 보세요.이제 통신사인 프랭크에게 그런 주소가 있는지 물어볼 수 있게 되었고, 프랭크는 단순하고 차분하게 보고할 것입니다.우리는 심지어 그가 이 위치가 실제로 존재한다면 얼마나 거리 밖에 있을지 추정할 것이라고 예상할 수 있습니다. 그것은 끝에서 약 2.5배 더 멀리 떨어져 있습니다.이 일들이 그를 화나게 하지는 않을 것입니다.하지만 만약 우리가 그에게 이 편지를 전달해달라고 요청하거나, 그곳에서 물건을 찾으라고 한다면, 그는 그의 불쾌감불응에 대해 상당히 솔직하게 말할 것입니다.

포인터는 주소일 뿐이고 주소는 숫자일 뿐입니다.

다음의 출력을 확인합니다.

void foo( void *p ) {
   printf(“%p\t%zu\t%d\n”, p, (size_t)p, p == (size_t)p);
}

유효하든 그렇지 않든 간에 원하는 만큼 많은 포인터로 호출합니다.플랫폼에서 실패하거나 (현대의) 컴파일러가 불만을 제기하는 경우에는 결과를 게시하십시오.

이제, 포인터는 단순히 숫자이기 때문에, 그것들을 비교하는 것은 필연적으로 유효합니다.어떤 의미에서는 이것이 바로 당신의 선생님이 보여주고 있는 것입니다.다음 문들은 모두 완벽하게 유효하며, 적절한! C이며, 포인터가 초기화될 필요가 없으므로 포인터가 포함된 값이 정의되지 않을 수 있음에도 불구하고 컴파일되면 문제가 발생하지 않고 실행됩니다.

  • 우리는 단지 계산하고 있을 뿐입니다.result 명확성을 위해, 그리고 그것인쇄하여 컴파일러로 하여금 중복되는 데드 코드를 계산하도록 강요합니다.
void foo( size_t *a, size_t *b ) {
   size_t result;
   result = (size_t)a;
   printf(“%zu\n”, result);
   result = a == b;
   printf(“%zu\n”, result);
   result = a < b;
   printf(“%zu\n”, result);
   result = a - b;
   printf(“%zu\n”, result);
}

물론 테스트 시점에서 a 또는 b가 정의되지 않은 경우(읽기: 제대로 초기화되지 않음) 프로그램이 잘못 구성되어 있지만, 이 부분은 논의의 이 부분과는 전혀 무관합니다.이러한 토막글은 다음 문장과 마찬가지로 관련된 포인터의 IN 유효성에도 불구하고 '표준'에 의해 완벽하게 컴파일되고 실행될 수 있도록 보장됩니다.

잘못된 포인터를 역참조할 때만 문제가 발생합니다.우리가 프랭크에게 유효하지 않은, 존재하지 않는 주소로 픽업이나 배달을 요청할 때.

임의 포인터가 주어졌을 때:

int *p;

이 문은 컴파일되고 실행되어야 합니다.

printf(“%p”, p);

... 반드시 이와 같이

size_t foo( int *p ) { return (size_t)p; }

... 이와 대조적으로 다음 두 가지는 여전히 쉽게 컴파일되지만 포인터가 유효하지 않는 한 실행실패합니다. 이는 단순히 현재 응용 프로그램이 액세스 권한을 부여받은 주소를 참조한다는 것을 의미합니다.

printf(“%p”, *p);
size_t foo( int *p ) { return *p; }

얼마나 미묘한 변화입니까?구분은 주소인 포인터 값과 해당 번호의 집 내용물 값의 차이에 있습니다.포인터가 연결된 주소에 액세스하기 전까지는 문제가 발생하지 않습니다.도로 너머로 소포를 배달하거나 주우려고 할 때...

또한, 필요한 유효성을 확립하기 위해 전술한 필요성을 포함하여 보다 복잡한 예에 대해서도 동일한 원칙이 반드시 적용됩니다.

int* validate( int *p, int *head, int *tail ) { 
    return p >= head && p <= tail ? p : NULL; 
}

관계형 비교와 산술은 동등성을 검정하는 데 동일한 효용을 제공하며, 원칙적으로 동등하게 유효합니다.그러나 이러한 계산 결과가 의미하는 바는 완전히 다른 문제이며, 정확히는 포함한 인용문에 의해 다루어진 문제입니다.

C에서 배열은 연속적인 버퍼로, 메모리 위치의 연속적인 선형 열입니다.그러한 단수 급수 내의 참조 위치들이 자연적으로 그리고 분명히 서로의 관계와 이 '배열'(단순히 베이스로 식별됨)에 있어서 의미가 있는 포인터들에 적용되는 비교와 산술.다음을 통해 할당된 모든 블록에도 정확히 동일하게 적용됩니다.malloc, 아니면sbrk. 이러한 관계는 암묵적이기 때문에 컴파일러는 이들 사이에 유효한 관계를 설정할 수 있으며, 따라서 계산이 예상되는 해답을 제공할 것이라고 확신할 수 있습니다.

별개의 블록 또는 배열을 참조하는 포인터에 대해 유사한 체조를 수행하는 것은 그러한 고유하고 명백한 유용성을 제공하지 않습니다.한 순간에 존재하는 관계가 무엇이든 간에 그 이후의 재할당에 의해 무효화될 수 있기 때문에, 그것은 변경될 가능성이 높고 심지어 반전될 수도 있습니다.이러한 경우 컴파일러는 이전 상황에서 자신이 가지고 있던 신뢰를 확립하는 데 필요한 정보를 얻을 수 없습니다.

하지만 프로그래머로서 그런 지식을 가지고 있을 수도 있습니다!그리고 어떤 경우에는 그것을 이용할 의무가 있습니다.

따라서 마저도 전적으로 유효하고 완벽하게 적절한 상황이 존재합니다.

사실, 그게 바로 그 점입니다.malloc그 자체는 대부분의 아키텍처에서 매립된 블록을 병합하는 작업을 시도할 때 내부적으로 수행해야 합니다.입니다 뒤에 입니다.sbrk; 더 명백하게, 자주, 더 상이한 엔티티에 대해, 더 비판적으로 -- 그리고 이것이 존재하는 플랫폼에도 관련이 있습니다.malloc아닐 수도 있습니다.그리고 그 중 C로 작성되지 않은 것은 몇 개입니까?

행동의 유효성, 보안 및 성공은 필연적으로 그것이 전제되고 적용되는 통찰력 수준의 결과입니다.

당신이 제시한 견적에서 커니건과 리치는 밀접한 관련이 있지만 그럼에도 불구하고 별개의 문제를 다루고 있습니다.언어한계정의하고, 최소한 오류가 있을 수 있는 구성을 탐지하여 사용자를 보호하기 위해 컴파일러의 기능을 어떻게 활용할 수 있는지 설명합니다.그들은 여러분의 프로그래밍 작업을 돕기 위해 메커니즘이 설계된 길이를 설명하고 있습니다.컴파일러는 당신의 하인이고 당신주인입니다.그러나 현명한 주인은 그의 여러 신하들의 능력에 아주 정통한 주인입니다.

이러한 맥락 속에서, 정의되지 않은 행동은 잠재적인 위험과 해악의 가능성을 나타내는 역할을 하며, 우리가 알고 있는 것처럼 임박하거나 돌이킬 수 없는 운명, 또는 세상의 종말을 암시하는 것은 아닙니다.그것은 단순히 우리,'컴파일러'의미하는 것은, 이것이 무엇인지에 대해 어떠한 추측도 할 수 없다는 것을 의미합니다. 또는 표현할 수도 있고, 이러한 이유로 우리는 이 문제에서 손을 떼기로 선택합니다.당사는 본 시설사용 또는 오용으로 인해 발생할 수 있는 어떠한 사고에 대해서도 책임을 지지 않습니다.

사실상, '이 점을 넘어서면, 카우보이: 당신은 당신 혼자입니다..’

당신의 교수님은 당신에게 더 섬세한 뉘앙스를 보여주려고 합니다.

그들이 자신들의 본보기를 만드는 데 얼마나 많은 정성을 기울였는지, 그리고 그것이 아직얼마나 부서지기 쉬운지 주목하십시오.주소를 가져감으로써a, 인에

p[0].p0 = &a;

컴파일러는 변수에 대한 실제 저장소를 레지스터에 배치하는 것이 아니라 할당하도록 강제됩니다.그러나 자동 변수이기 때문에 프로그래머는 해당 변수가 할당된 위치에 대한 제어권이 없으므로 다음 변수에 대해 유효한 추측을 할 수 없습니다.그래서.a 코드가 예상대로 작동하려면 0과 동일하게 설정해야 합니다.

이 줄만 바꾸는 것:

char a = 0;

다음 항목에 대해:

char a = 1;  // or ANY other value than 0

프로그램의 동작이 정의되지 않게 합니다.최소한 첫 번째 답은 이제 1이 될 것입니다. 그러나 문제는 훨씬 더 불길합니다.

이제 코드는 재앙을 불러옵니다.

여전히 완벽하게 유효하고 표준에 부합하지만, 현재는 형식이 잘못되어 컴파일이 확실하지만 여러 가지 이유로 실행에 실패할 수 있습니다.현재로서는 여러 가지 문제가 있습니다. 컴파일러인식할 수 있는 문제는 없습니다.

strcpy입니다 합니다.a을 할 null 를 합니다.

p1포인터가 정확히 10바이트의 블록으로 초기화되었습니다.

  • 한다면a블록의 끝에 우연히 배치되고 프로세스는 다음 것에 접근할 수 없습니다. p0[1]의 바로 다음 판독값은 segfault를 유도합니다.이 시나리오는 x86 아키텍처에서는 가능성이 낮지만 가능합니다.

  • a 접근할 수 있으며 읽기 오류는 발생하지 않지만 프로그램은 여전히 불행으로부터 저장되지 않습니다.

  • 주소에서 시작하는 10 안에 0바이트가 발생하는 경우a, 그때는 아직 살아있을지도 모릅니다.strcpy정지할 것이고 적어도 쓰기 위반은 발생하지 않을 것입니다.

  • 잘못 읽은 것은 아니지만 이 10의 스팬에서 0바이트가 발생하지 않는다면,strcpy계속해서 다음과 같이 할당된 블록 이상으로 쓰기를 시도합니다.malloc.

    • 이 영역이 프로세스에 의해 소유되지 않으면 즉시 segfault를 트리거해야 합니다.

    • 프로세스가 다음 블록을 소유한 경우, 오류를 감지할 수 없고 신호가 발생하지 않으며 '작동'하는 것처럼 보일 수 있지만 실제로는 다른 데이터, 할당자의 관리 구조를 덮어쓰게 됩니다.또는 (특정 운영 환경에서) 코드화할 수도 있습니다.

이것이 포인터 관련 버그를 추적하기가 매우 어려울 수 있는 이유입니다.다른 사람이 쓴 수천 줄의 복잡하게 얽힌 코드 안에 깊이 묻혀있는 이 선들을 상상해 보세요. 그러면 당신은 그 속을 파헤치도록 지시받습니다.

그럼에도 불구하고, 프로그램은 여전히 컴파일되어야 합니다. 왜냐하면 그것은 완벽하게 유효하고 표준 적합한 C로 남아있기 때문입니다.

이런 종류의 오류들, 어떤 표준도, 어떤 컴파일러도 경계하지 않는 것을 보호할 수 없습니다.나는 그것이 바로 그들이 당신에게 가르치려는 의도라고 생각합니다.

편집증적인 사람들은 이러한 문제적인 가능성을 처리하기 위해 끊임없이 C의 본질바꾸려고 하고 그래서 우리를 우리 자신으로부터 구하려고 합니다. 그러나 그것은 솔직하지 못한 것입니다.이것은 우리가 을 추구하고 기계에 대한 보다 직접적이고 포괄적인 통제가 가능한 자유를 얻기 위해 선택할 때 우리가 받아들여야책임입니다.성과에 있어서 완벽함을 추구하는 사람들은 결코 그 이하의 것을 받아들이지 않을 것입니다.

휴대성과 휴대성이 나타내는 일반성은 근본적으로 별개의 고려 사항이며 표준이 다루고자 하는 모든 은 다음과 같습니다.

이 문서는 C 프로그래밍 언어로 표현된 프로그램의 형태와 해석을 명시합니다. 목적다양한 컴퓨팅 시스템에서 C 언어 프로그램의 휴대성, 신뢰성, 유지보수성 효율적인 실행촉진하는 것입니다.

그렇기 때문에 언어 자체의 정의기술적 사양과 구별되는 것이 완벽하게 적절합니다.많은 사람들이 일반성예외적이고 모범적이라고 믿는 것과 반대로.

결론:

  • 포인터 자체를 조사하고 조작하는 것은 항상 유효하고 종종 효과적입니다.결과에 대한 해석은 의미가 있을 수도 있고 없을 수도 있지만 포인터가 참조되지 않고 에 연결된 주소에 액세스하려고 시도할 때까지 재앙은 초대되지 않습니다.

만약 이것이 사실이 아니었다면, 우리가 알고 있는 것처럼 그리고 그것을 사랑하는 프로그래밍은 가능하지 않았을 것입니다.

언급URL : https://stackoverflow.com/questions/59516346/how-does-pointer-comparison-work-in-c-is-it-ok-to-compare-pointers-that-dont-p

반응형