[c] C에서 포인터 비교는 어떻게 작동합니까? 같은 배열을 가리 키지 않는 포인터를 비교해도 괜찮습니까?

K & R (C Programming Language 2nd Edition) 5 장에서 다음을 읽었습니다.

첫째, 특정 상황에서 포인터를 비교할 수 있습니다. 경우 pq다음의 관계가 좋아 점 같은 배열의 구성원에 ==, !=, <, >=제대로, 등 일을.

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

그러나이 코드를 시도했을 때

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

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

1 화면에 인쇄됩니다.

우선, 나는 때문에 나는, 어떤 종류 또는 오류 또는 정의되지 얻을 것이라고 생각 pt하고 px(나의 이해 적어도) 같은 배열을 가리키는되지 않습니다.

또한 pt > px두 포인터가 스택에 저장된 변수를 가리키고 스택이 커지므로 메모리 주소 tx?의 주소 보다 큽니다 . 어느 것이 pt > px사실입니까?

malloc이 도입되면 더 혼란스러워집니다. 8.7 장의 K & R에서도 다음과 같이 쓰여집니다.

그러나 여전히 반환되는 다른 블록에 대한 포인터를 sbrk의미있게 비교할 수 있다는 가정이 여전히 있습니다. 이것은 배열 내에서만 포인터 비교를 허용하는 표준에 의해 보장되지 않습니다. 따라서이 버전의 버전은 malloc일반적인 포인터 비교가 의미가있는 머신 중에서 만 이식 가능합니다.

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

예를 들어, 다음 코드는 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, 10입니다.

(내 교수는 시험에 대한 면책 ​​조항을 포함하여 Ubuntu Linux 16.04, 64 비트 버전 프로그래밍 환경에 대한 질문)

(편집자 주 : SO가 더 많은 태그를 허용 한 경우, 마지막 부분은 , 및 어쩌면 보증 합니다. 질문 / 클래스의 요점이 특히 휴대용 C가 아닌 저수준 OS 구현 세부 정보 인 경우)



답변

받는 따르면 C11 표준 , 관계 연산자는 <, <=, >, 및 >=는 동일한 어레이 또는 구조체 객체의 요소 포인터를 사용할 수있다. 이것은 섹션 6.5.8p5에 나와 있습니다.

두 개의 포인터를 비교할 때 결과는 지정된 객체의 주소 공간에서 상대 위치에 따라 달라집니다. 객체 유형에 대한 두 개의 포인터가 모두 동일한 객체를 가리 키거나 둘 다 동일한 배열 객체의 마지막 요소를 지나는 하나를 가리키는 경우, 이들은 동일하게 비교됩니다. 지정된 개체가 동일한 집계 개체의 멤버 인 경우 나중에 선언 된 구조체 멤버에 대한 포인터는 구조체에서 앞서 선언 된 멤버에 대한 포인터보다 크게 비교하고, 아래 첨자 값이 더 큰 배열 요소에 대한 포인터는 동일한 배열의 요소에 대한 포인터보다 크게 비교합니다. 아래 첨자 값이 낮습니다. 동일한 공용체 객체의 멤버에 대한 모든 포인터는 동일하게 비교됩니다.

이 요구 사항을 충족하지 않는 비교는 정의되지 않은 behavior를 호출합니다 . 즉, 결과에 의존하여 반복 할 수 없다는 의미입니다.

특별한 경우 두 로컬 변수의 주소와 로컬 및 동적 주소의 주소를 비교하기 위해 작업이 “작동”하는 것처럼 보이지만 결과가 코드와 관련이없는 것처럼 변경되어 결과가 변경 될 수 있습니다 또는 다른 최적화 설정으로 동일한 코드를 컴파일 할 수도 있습니다. 정의되지 않은 동작으로, 코드는 이유만으로 수있는 오류가 충돌하거나 발생이 의미하는 것은 아니다 것이다 .

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

항등 연산자 ==!=그러나 이러한 제한이 없습니다. 호환 가능한 유형 또는 NULL 포인터에 대한 두 포인터 사이에서 사용할 수 있습니다. 사용 그래서 ==또는 !=유효한 C 코드를 생성 할 당신의 두 예제입니다.

그러나, 함께 ==그리고 !=당신은 예기치 않은 아직은 잘 정의 된 결과를 얻을 수 있습니다. 관련없는 포인터의 동등 비교가 true로 평가 될 수 있습니까?를 참조하십시오 . 이에 대한 자세한 내용은.

교수가 제공 한 시험 문제와 관련하여 다음과 같은 몇 가지 잘못된 가정이 있습니다.

  • 플랫 메모리 모델은 주소와 정수 값 사이의 일대일 대응이 존재합니다.
  • 변환 된 포인터 값은 정수 유형에 맞습니다.
  • 구현시 정의되지 않은 동작으로 제공되는 자유를 활용하지 않고 비교를 수행 할 때 포인터를 정수로 취급합니다.
  • 스택이 사용되고 로컬 변수가 거기에 저장됩니다.
  • 힙은 할당 된 메모리를 가져 오는 데 사용됩니다.
  • 스택 (및 로컬 변수)은 힙 (및 할당 된 객체)보다 높은 주소에 나타납니다.
  • 해당 문자열 상수는 힙보다 낮은 주소에 나타납니다.

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

또한 두 strcpy피연산자 모두 호출 할 때 정의되지 않은 동작을 나타냅니다 . 오른쪽 피연산자 (일부 경우)는 null로 끝나는 문자열이 아닌 단일 문자를 가리 키므로 함수가 지정된 변수의 경계를지나 읽습니다.


답변

포인터를 동일한 유형의 두 개의 개별 배열과 비교하는 주요 문제는 배열 자체가 특정 상대 위치에 배치 될 필요가 없다는 것입니다. 하나는 다른 전후에 끝날 수 있습니다.

우선, pt px가 (적어도 내 이해에서) 동일한 배열을 가리 키지 않기 때문에 정의되지 않았거나 일부 유형이나 오류가 발생한다고 생각했습니다.

아니요, 결과는 구현 및 기타 예측할 수없는 요소에 따라 다릅니다.

또한 두 포인터가 스택에 저장된 변수를 가리키고 스택이 커져서 t의 메모리 주소가 x의 메모리 주소보다 크기 때문에 pt> px입니다. pt> px가 사실 인 이유는 무엇입니까?

반드시 스택이 필요한 것은 아닙니다 . 존재하면 자랄 필요가 없습니다. 자랄 수있었습니다. 기괴한 방식으로 비 연속적 일 수 있습니다.

또한 두 포인터 사이의 포인터 산술은 포인터가 저장하는 메모리 주소를 사용하기 때문에 개별적으로 가리키는 위치에 관계없이 괜찮습니다.

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

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

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

마지막 문장이 중요합니다. 공간을 절약하기 위해 관련이없는 경우를 줄 이면서 우리 에게 중요한 한 가지 경우가 있습니다. 동일한 구조체 / 집계 객체 1의 일부가 아닌 두 개의 배열이 있으며 두 배열에 대한 포인터를 비교하고 있습니다. 이것은 정의되지 않은 동작 입니다.

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

1 동일한 구조체의 일부인 두 개의 다른 배열에 대한 포인터를 비교할 수 있습니다. 두 배열이 동일한 집계 객체의 일부인 구조체 (struct)에 속하기 때문입니다.


답변

그런 다음 무엇을 물었다

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는 모두 x10 개의 요소 인 경우에도 항상 4를 반환 하고 y바로 뒤에오고 i0을 발생하여 비교가 true이고 p[0]값 1로 쓰여지 는 코드를 생성 합니다. 함수 *p = 1;가로 대체되었습니다 x[10] = 1;. 후자의 코드는 컴파일러가에 해당하는 것으로 해석 *(x+10)하면 동일 *(y+i)하지만 불행히도 다운 스트림 최적화 단계 에서는 11 개 이상의 요소가있는 x[10]경우에만 액세스가 정의 x되어 액세스가 영향을받을 수 없음을 인식 y합니다.

컴파일러가 표준에서 설명하는 포인터 평등 시나리오를 사용하여 “크리에이티브”를 얻을 수 있다면 표준이 요구 사항을 부과하지 않는 경우 더 창조적이되는 것을 자제하지 않을 것입니다.


답변

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


답변

도발적인 질문!

이 스레드에서 응답과 의견을 객관적으로 스캔해도 간단하고 간단한 쿼리가 얼마나 감동적 인지 알 수 있습니다.

놀라운 일이 아닙니다.

틀림없이, 포인터개념과 사용대한 오해 는 일반적으로 프로그래밍에서 심각한 실패 의 주요 원인 을 나타냅니다 .

이러한 현실에 대한 인식은 특별히 다루기 위해 그리고 바람직하게는 피하기 위해 특별히 고안된 언어의 편재성에서 쉽게 명백하다 포인터가 완전히 야기하는 문제 . C ++ 및 C, Java 및 그 관계, Python 및 기타 스크립트의 파생물을 단순히 더 두드러지고 널리 사용되는 스크립트로 생각하고 문제를 다루는 정도가 다소 중요하다고 생각하십시오.

, 기본 원리의 깊은 이해를 개발, 따라서해야합니다 관련해당 열망하는 모든 개인 우수성을 프로그래밍에 – 특히 시스템 수준에서 .

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

그리고 C의 특성상이 탐사를위한 편리한 수단입니다. 어셈블리보다 덜 명확하지만 (쉽게 이해하기는 쉽지만) 실행 환경에 대한 더 깊은 추상화에 기반한 언어보다 훨씬 더 명확합니다.

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

이 특성은 그것을 만들기위한 주로 담당하는 선택의 언어 에 대한 장치 드라이버 , 운영 체제 코드 및 임베디드 구현. 또한, 최적의 효율 이 가장 중요한 응용 분야에서 당연히 선호되는 대안입니다 . 여기서 생존과 멸종의 차이를 의미하므로 사치와는 반대로 필수품 입니다. 그러한 경우에, 이식성 의 매력적인 편리함은 모든 매력을 잃고, 최소 공통 분모의 결점없는 성능을 선택 하는 것은 생각할 수없는 해로운 선택이된다.

무엇 C를 만드는 – 및 그 유도체의 일부 – 아주 특별한, 그것은 것입니다 는 사용자 완전한 제어 – 즉 그들이 원하는 무엇을 때 – 없이 부과 관련 책임을 그렇지 않은 경우 그들에게. 그럼에도 불구하고, 결코 이상 제공하지 않습니다 절연체의 얇은 로부터 기계 적절한 사용 그런즉 요구 정확한 이해 의 개념의 포인터를 .

본질적으로, 귀하의 질문에 대한 답변은 의심의 여지없이 간단하고 만족스럽게 달콤합니다. 제공 하지만, 하나는 필요한 첨부 중요성 에 대한 모든 개념 이 문을 :

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

전자는 둘 다 변함 안전 하고 잠재적으로 적절한 후자 캔 오직 일 동안 적당한 가되었을 때에 설정 으로 안전한 . 놀랍게도 -어떤 사람들에게는 후자의 타당성을 확립하는 것은 전자에 의존 하고 요구 합니다.

물론, 혼란의 일부는 포인터의 원칙에 본질적으로 존재하는 재귀의 영향과 내용을 주소와 구별하는 데 제기되는 어려움에서 발생합니다.

당신은 아주 정확하게 추측했습니다.

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

그리고 여러 기고자들이 확인했습니다 . 때로는 복잡한 숫자에 더 가까운 것이지만 여전히 숫자에 지나지 않습니다.

이 경합이 여기에 접수 된 재미있는 경건은 프로그래밍보다 인간의 본성에 대해 더 많은 것을 보여 주지만 여전히 주목할 가치가 있습니다. 아마도 우리는 나중에 그렇게 할 것입니다 …

한 의견이 암시하기 시작하면서; 이 모든 혼란과 욕구 는 안전한 것에서 유효한 것이 무엇인지 식별해야 할 필요성에서 비롯 되지만 이는 지나치게 단순화 된 것입니다. 우리는 또한 기능적 이며 신뢰할 수있는 것 , 실용적 이며 적절한 것 , 그리고 더 나아가 여전히 특정 상황 에서 무엇이 더 일반적인 의미 에서 적절한 것인지 구별해야합니다 . 언급 할 필요없는; 적합성타당성 의 차이 .

이를 위해, 우리는 먼저해야 할 감사 정확하게 어떤 포인터 입니다 .

  • 당신은 개념에 대한 확고한 이해를 보여 주었고, 다른 사람들처럼이 삽화들이 엄청나게 단순하다는 것을 알 수 있지만, 여기에서 명백한 혼란의 정도는 설명의 단순성을 요구 합니다.

몇 가지 지적한 바와 같이, 포인터 라는 용어 는 단순히 색인에 대한 특별한 이름 이므로 다른 숫자에 지나지 않습니다 .

이것은 현대의 모든 주류 컴퓨터가 반드시 숫자독점적 으로 작동 하는 이진 기계 라는 사실을 고려할 때 이미 자명 해야 합니다 . 퀀텀 컴퓨팅 은이를 바꿀 있지만 그럴 가능성은 적고 아직 오지 않았습니다.

기술적으로, 앞에서 언급했듯이 포인터 는 더 정확한 주소입니다 . 자연스럽게 주택의 ‘주소’또는 거리의 음모와 상관 관계가 있다는 보람있는 비유를 소개하는 명백한 통찰력.

  • A의 플랫 메모리 모델 : 전체 시스템 메모리는 하나의 선형 순서로 구성되어 같은 도로에 도시의 거짓말에있는 모든 주택, 그리고 모든 집은 유일하게 혼자의 번호로 식별됩니다. 유쾌하게 간단합니다.

  • 에서는 분할 방식 : 번째 도로 계층 조직 복합 주소가 필요되도록 번째 집의 이상 도입된다.

    • 일부 구현은 여전히 ​​더 복잡하고 별개의 ‘도로’의 전체가 연속적인 시퀀스에 합산 될 필요는 없지만 , 그 중 어느 것도 근본적인 것에 대해 아무것도 변화 시키지 않습니다 .
    • 우리는 그러한 모든 계층 적 링크를 평평한 조직으로 다시 분해 할 수 있습니다. 조직이 복잡할수록 더 많은 홉을 뚫어야하지만 그렇게 해야합니다. 있지만 가능 합니다. 실제로 이것은 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에 관련된 모든 포인터의 -validity.

잘못된 포인터를 역 참조 할 때만 문제가 발생합니다 . Frank에게 존재하지 않는 유효하지 않은 주소를 수령 또는 전달하도록 요청할 때.

임의의 포인터가 주어지면 :

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. 때문에 이러한 관계가 암시 , 컴파일러는 그들 사이에 올바른 관계를 구축 할 수있다, 따라서 될 수 있습니다 확신 계산 예상 답변을 제공 할 것입니다.

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

당신은 , 그러나, 프로그래머, 수도 등의 지식을 가지고! 그리고 어떤 경우에는 그것을 이용해야합니다.

ARE 있는 상황에 따라서 에도이 전적으로 유효 완벽하게 올바른은.

사실, 즉 정확히 어떤 malloc아키텍처의 대부분에 – 자체 것은 시간 재생 블록을 병합하려고 할 때 내부적으로 관련이있다. 운영 체제 할당 자도 마찬가지입니다 sbrk. 만약 더 분명 , 자주더 서로 다른 개체, 더 비판적으로 – 또한이 플랫폼에 관련 malloc되지 않을 수 있습니다. 그리고 C로 작성 되지 않은 것들이 몇 개 입니까?

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

당신이 제공 한 인용문에서 Kernighan과 Ritchie는 밀접한 관련이 있지만 별도의 문제를 다루고 있습니다. 그들은되는 정의 제한언어를 , 당신은 적어도 잠재적으로 잘못된 구조를 감지하여 사용자를 보호하기 위해 컴파일러의 기능을 활용할 수있는 방법을 설명. 그들은 프로그래밍 작업을 돕기 위해 메커니즘이 설계 할 수있는 길이를 설명하고 있습니다. 컴파일러는 당신의 종이며, 당신주인입니다. 그러나 현명한 주인은 다양한 종들의 능력에 친숙한 사람입니다.

이러한 맥락에서, 정의되지 않은 행동 은 잠재적 위험과 위해 가능성을 나타내는 역할을합니다. 우리가 알고있는 임박하고 돌이킬 수없는 운명 또는 세상의 끝을 암시하지 않습니다. 그것은 단지 우리가 ‘컴파일러를 의미’한다는 것은이 일이 무엇인지, 대표 할 수 있는지에 대한 추측을 할 수 없다는 것을 의미합니다 . 우리는이 시설의 사용 또는 오용으로 인해 발생할 수있는 오해에 대해 책임을지지 않습니다 .

사실상, 그것은 단순히 이렇게 말합니다 : ‘이 시점을 넘어서, 카우보이 : 당신은 당신 자신에 있습니다 …’

교수님이 더 미세한 뉘앙스 를 보여 주려고 합니다.

무엇을 주목하십시오 그들이 모범을 보이기 위해 주의를 기울. 어떻게 취성 여전히 있다. 의 주소를 복용함으로써a에서,

p[0].p0 = &a;

컴파일러는 변수에 대한 실제 스토리지를 레지스터에 배치하지 않고 할당하도록 강제됩니다. 그것은 자동 변수는, 그러나, 프로그래머가없는 것을 이상 통제 이이 할당을하고,없는, 그래서 그것을 따를 것에 대해 유효한 추측을 할 수 있습니다. 그렇기 때문에 코드가 예상대로 작동하려면 0으로 설정 a 해야합니다 .

이 줄만 변경하면됩니다 :

char a = 0;

이에:

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

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

이제 코드는 재난을 불러 일으 킵니다.

여전히 완벽하게 유효 하고 표준을 준수 하지만 , 이제는 형식이 잘못되어 컴파일해야하지만 다양한 이유로 실행에 실패 할 수 있습니다. 지금이없는 여러 문제 – 아무도 그중 컴파일러 입니다 인식하고 있습니다.

strcpy의 주소에서 시작 a하여이 값을 넘어 서면 바이트를 소비하고 널 (null)이 발생할 때까지 바이트 단위로 전송합니다.

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

  • 경우 a블록의 끝에 위치 할 일이 프로세스는 바로 다음 읽기 다음과 무엇에 대한 액세스 권한이 없습니다 – P0의를 [1] – 세그먼트 폴트를 이끌어내는 것입니다. 이 시나리오는 가능성이 x86 아키텍처에 만 가능.

  • 주소를 벗어난 영역 a 액세스 할 수있는 경우 읽기 오류가 발생하지 않지만 프로그램은 여전히 ​​불행에서 저장되지 않습니다.

  • 경우 0 바이트가 발생 의 주소에서 시작하는 열 내에서 발생하는 것으로 a,이 후 여전히 생존 strcpy중단되고 적어도 우리는 쓰기 위반 고통을하지 않습니다.

  • amiss 읽기에 대한 결함 이 없지만 10의이 범위에서 0 바이트 가 발생 하지 않으면 에 의해 할당 된 블록을 넘어서 strcpy계속 쓰기 를 시도합니다 malloc.

    • 프로세스가이 영역을 소유하지 않으면 segfault가 즉시 트리거되어야합니다.

    • 프로세스 다음 블록 소유 하면 여전히 더 비참하고 미묘한 상황이 발생 하므로 오류 감지 할 수 없으며 신호를 발생 시킬 수 없으므로 여전히 ‘작동’할 수 있습니다 . 실제로 다른 데이터, 할당 자의 관리 구조 또는 코드 (일부 운영 환경)를 덮어 쓰게 됩니다 .

입니다 포인터 관련 버그가 그렇게 할 수 있습니다 하드 추적. 이 줄이 복잡한 다른 코드의 수천 줄 안에 깊숙이 묻혀 있다고 상상해보십시오.

그럼에도 불구하고 , 프로그램이 있어야 이 남아 여전히 컴파일 완벽하게 유효 하고 표준을 준수하는 C로.

오류, 이러한 종류의 어떤 표준없음 컴파일러는 부주의에 대하여 보호 할 수 없습니다. 나는 그것이 그들이 당신에게 가르치려고하는 것이라고 정확히 상상합니다.

편집증 사람들 은 이러한 문제 가능성을 없애기 위해 C 의 본질 을 끊임없이 변화 시키려고 노력 하며 따라서 우리 자신으로부터 우리를 구합니다 . 그러나 그것은 불분명하다 . 이것은 우리가 을 추구 하고 기계 를보다 직접적이고 포괄적으로 통제 할 수있는 자유 를 얻기로 결정할 때 받아 들여야책임 입니다. 완벽한 퍼포먼스를 추구하는 사람들은 결코 더 적은 것을 받아들이지 않을 것입니다.

이식성 과그것이 나타내는 일반성 은 근본적으로 분리 된 고려 사항이며 표준 이 다루고 자하는 모든 것:

이 문서는 형식을 지정하고 프로그래밍 언어 C로 표현 된 프로그램의 해석을 확립합니다. 그 목적다양한 컴퓨팅 시스템 에서 C 언어 프로그램의 이식성 , 신뢰성, 유지 관리 및 효율적인 실행촉진 하는 입니다 .

그렇기 때문에 언어 자체 의 정의기술 사양구별 되는 것이 완벽하게 적합한 이유 입니다. 많은 것과는 달리이 생각하는 것 보편성이 있다 정반대탁월한예시 .

결론적으로:

  • 포인터 자체를 검사하고 조작하는 것은 항상 유효 하며 종종 유익 합니다. 결과의 해석은 의미가 있거나 의미가 없을 수 있지만, 포인터가 역 참조 될 때까지 재앙이 결코 초대되지 않습니다 . 접근을 시도 할 때까지연결된 주소 .

이것이 사실이 아니었다. 우리가 알고 사랑하는 프로그래밍 은 불가능했을 것이다.


답변

포인터는 컴퓨터의 다른 모든 것과 마찬가지로 정수입니다. 당신은 절대적으로 그들을 비교할 수 <>충돌하는 프로그램을 일으키는 원인이없이 생산 결과. 즉, 표준은 이러한 결과가 의미를 보장하지 않습니다 배열 비교 이외의 .

스택 할당 변수의 예에서 컴파일러는 해당 변수를 레지스터 또는 스택 메모리 주소에 자유롭게 할당 할 수 있습니다. 같은 비교 <하고 >, 따라서 컴파일러 또는 아키텍처에서 일관되지 않습니다. 그러나, ==그리고 !=그렇게 제한되지 않으며, 포인터 비교 평등은 유효하고 유용한 작업입니다.