나는 당신의 질문을 두 가지 질문으로 해석 할 것입니다 : 1) 왜 ->
존재 하는지 , 그리고 2) 왜 .
포인터를 자동으로 역 참조하지 않는가. 두 질문에 대한 답은 역사적 뿌리를 가지고 있습니다.
왜 ->
존재 하는가?
(I를위한 "CRM으로 참조 할 C 언어의 최초의 버전 중 하나에서 C 참조 설명서 5 월 1975 년 6 판 유닉스와 함께"), 연산자 ->
와 동의어하지, 매우 배타적 인 의미를 가지고 *
및 .
조합
CRM에 의해 기술 된 C 언어는 여러면에서 현대 C와는 매우 달랐습니다. CRM struct 멤버는 바이트 오프셋 이라는 글로벌 개념을 구현했으며 , 이는 유형 제한없이 모든 주소 값에 추가 될 수 있습니다. 즉, 모든 구조체 멤버의 모든 이름은 독립적 인 글로벌 의미를 가졌으므로 고유해야했습니다. 예를 들어 선언 할 수 있습니다
struct S {
int a;
int b;
};
name a
은 오프셋 0을 나타내며 name b
은 오프셋 2를 나타냅니다 ( int
크기 2의 유형이 있고 패딩이 없다고 가정 ). 언어는 번역 단위의 모든 구조체의 모든 멤버가 고유 한 이름을 갖거나 동일한 오프셋 값을 나타내도록 요구했습니다. 예를 들어 동일한 번역 단위로 추가로 선언 할 수 있습니다.
struct X {
int a;
int x;
};
이름 a
이 지속적으로 오프셋 0을 의미하기 때문에 괜찮습니다. 그러나이 추가 선언
struct Y {
int b;
int a;
};
a
오프셋 2 및 b
오프셋 0 으로 "재정의" 를 시도했기 때문에 공식적으로 유효하지 않습니다 .
그리고 이것은 ->
연산자가 들어오는 곳 입니다. 모든 구조체 멤버 이름은 자체적으로 충분한 글로벌 의미를 갖기 때문에 언어는 다음과 같은 표현을 지원했습니다.
int i = 5;
i->b = 42; /* Write 42 into `int` at address 7 */
100->a = 0; /* Write 0 into `int` at address 100 */
첫 번째 할당은 컴파일러에 의해 "주소 가져 오기 5
, 오프셋 추가 2
및 결과 주소 42
의 int
값 할당 "으로 해석되었습니다 . 즉, 위의 주소 값에 할당 42
됩니다 . 이 사용은 왼쪽의 표현 유형에 신경 쓰지 않았습니다. 왼쪽은 rvalue 숫자 주소 (포인터 또는 정수)로 해석되었습니다.int
7
->
속임수 이런 종류의 불가능했다 *
및 .
조합. 넌 못해
(*i).b = 42;
왜냐하면 *i
이미 잘못된 표현 이기 때문 입니다. *
연산자는 별개이므로 .
피연산자에 강요하며 더 엄격한 타입 요건. 이 제한을 해결하는 기능을 제공하기 위해 CRM ->
은 왼쪽 피연산자의 유형과 무관 한 연산자를 도입했습니다 .
키스는 코멘트에 언급 한 바와 같이,이 차이 사이 ->
와 *
+ .
조합은 어떤 CRM은 7.1.8의 "요구 사항의 완화"로 언급된다 : 그 요건의 완화를 제외하고 E1
,식이 포인터 유형이 될 E1−>MOS
정확히 동일합니다(*E1).MOS
나중에 K & R C에서 CRM에 원래 설명 된 많은 기능이 크게 재 작업되었습니다. "전역 오프셋 식별자로서의 구조체 멤버"라는 개념은 완전히 제거되었습니다. 그리고 ->
운영자의 기능은 기능 *
과 .
조합 의 기능과 완전히 동일 해졌습니다 .
.
포인터를 자동으로 역 참조 할 수없는 이유는 무엇 입니까?
언어의 CRM 버전에서도 .
연산자 의 왼쪽 피연산자 는 lvalue 이어야했습니다 . 그것은 그 피연산자에 대해 부과 된 유일한 요구 사항 이었습니다 (그리고 ->
위에서 설명한 것처럼와 다른 점 이었습니다). CRM 에서는 왼쪽 피연산자가 구조체 유형을 갖도록 요구 하지 않았습니다.
. 그것은 단지 lvalue, 모든 lvalue가 필요했습니다 . 이것은 CRM 버전의 C에서 다음과 같은 코드를 작성할 수 있음을 의미합니다.
struct S { int a, b; };
struct T { float x, y, z; };
struct T c;
c.b = 55;
이 경우 컴파일러는 type 이라는 필드가 없더라도 이라고 알려진 연속 메모리 블록에서 바이트 오프셋 2에 55
위치한 int
값에 기록 합니다 . 컴파일러는 실제 유형에 전혀 신경 쓰지 않습니다 . 그것이 염려 한 것은 lvalue라는 것입니다. 일종의 쓰기 가능한 메모리 블록입니다.c
struct T
b
c
c
이 작업을 수행 한 경우
S *s;
...
s.b = 42;
코드는 유효한 것으로 간주되며 ( s
lvalue 이기 때문에 ) 컴파일러는 바이트 오프셋 2에서 포인터 s
자체에 데이터를 쓰려고 시도 합니다. 그런 문제에 대해서는 관심이 없었습니다.
즉, 해당 언어 버전 .
에서 포인터 유형의 연산자 오버로드에 대한 제안 아이디어가 작동하지 않습니다. 연산자 .
는 포인터와 함께 사용할 때 (lvalue 포인터 또는 lvalue가 전혀 없음) 이미 매우 특정한 의미를가집니다. 의심 할 여지없이 매우 이상한 기능이었습니다. 그러나 당시에는 그곳에있었습니다.
물론,이 이상한 기능은 .
C-K & R C의 재 작업 된 버전에서 포인터 에 대해 오버로드 된 연산자 (제안한대로)를 도입하는 것에 대한 강력한 이유 가 아닙니다. 그러나 아직 완료되지 않았습니다. 그 당시에는 CRM 버전 C로 작성된 일부 레거시 코드가 지원되어야했습니다.
(1975 C 참조 매뉴얼의 URL이 안정적이지 않을 수 있습니다. 약간의 차이가있는 다른 사본이 여기 있습니다 .)