도발적인 질문!
이 스레드에서 응답과 의견을 객관적으로 스캔해도 간단하고 간단한 쿼리가 얼마나 감동적 인지 알 수 있습니다.
놀라운 일이 아닙니다.
틀림없이, 포인터 의 개념과 사용 에 대한 오해 는 일반적으로 프로그래밍에서 심각한 실패 의 주요 원인 을 나타냅니다 .
이러한 현실에 대한 인식은 특별히 다루기 위해 그리고 바람직하게는 피하기 위해 특별히 고안된 언어의 편재성에서 쉽게 명백하다 포인터가 완전히 야기하는 문제 . 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 언어 프로그램의 이식성 , 신뢰성, 유지 관리 성 및 효율적인 실행 을 촉진 하는 것 입니다 .
그렇기 때문에 언어 자체 의 정의 및 기술 사양 과 구별 되는 것이 완벽하게 적합한 이유 입니다. 많은 것과는 달리이 생각하는 것 보편성이 있다 정반대 에 탁월한 및 예시 .
결론적으로:
- 포인터 자체를 검사하고 조작하는 것은 항상 유효 하며 종종 유익 합니다. 결과의 해석은 의미가 있거나 의미가 없을 수 있지만, 포인터가 역 참조 될 때까지 재앙이 결코 초대되지 않습니다 . 접근을 시도 할 때까지연결된 주소 .
이것이 사실이 아니었다. 우리가 알고 사랑하는 프로그래밍 은 불가능했을 것이다.
C
무엇과 안전 에C
. 두 개의 포인터를 비교하면 동일한 형태로 , 그러나 항상 (예를 들어, 지 어떤지를 검사) 할 수있는 포인터 연산을 이용하여 비교>
및 것이<
아니라 안전하게 사용될 때 내에 주어진 어레이 (또는 메모리 블록).