C에 화살표 (->) 연산자가 존재하는 이유는 무엇입니까?
도트 ( .
) 연산자는 구조체 ->
의 멤버에 액세스하는 데 사용되고 C 의 화살표 연산자 ( )는 해당 포인터가 참조하는 구조체의 멤버에 액세스하는 데 사용됩니다.
포인터 자체에는 도트 연산자로 액세스 할 수있는 멤버가 없습니다 (실제로는 가상 메모리의 위치를 설명하는 숫자이므로 멤버가 없습니다). 따라서 포인터 (포인팅 시간 afaik에서 컴파일러에 알려진 정보)에서 포인터를 사용하는 경우 도트 연산자를 자동으로 역 참조하도록 점 연산자를 정의한 경우 모호성이 없습니다.
그렇다면 언어 제작자들이 왜 이처럼 불필요한 연산자를 추가하여 일을 더 복잡하게하기로 결정 했습니까? 큰 디자인 결정은 무엇입니까?
나는 당신의 질문을 두 가지 질문으로 해석 할 것입니다 : 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 ->
은 왼쪽 피연산자의 유형과 무관 한 연산자를 도입했습니다 .
Keith가 의견에서 언급했듯이 ->
, *
+ 와 + .
조합의 차이 는 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이 안정적이지 않을 수 있습니다. 약간의 차이가있는 다른 사본은 여기에 있습니다 .)
역사적 (좋은 이미보고 된) 이유 외에도 연산자 우선 순위에는 약간의 문제가 있습니다. 점 연산자는 스타 연산자보다 우선 순위가 높습니다. 따라서 구조체에 대한 포인터를 포함하는 구조체에 대한 포인터를 포함하는 구조체가있는 경우이 두 가지가 동일합니다.
(*(*(*a).b).c).d
a->b->c->d
그러나 두 번째는 분명히 더 읽기 쉽습니다. 화살표 연산자는 우선 순위가 가장 높으며 (점과 동일) 왼쪽에서 오른쪽으로 연결됩니다. 필자는 구조체와 구조체에 대한 포인터에 도트 연산자를 사용하는 것보다 명확하다고 생각합니다. 왜냐하면 선언에서 보지 않아도 표현식의 유형을 알기 때문에 다른 파일에있을 수도 있기 때문입니다.
C는 또한 모호한 것을 만들지 않는 데 능숙합니다.
점이 과부하되어 두 가지를 모두 의미 할 수는 있지만 화살표는 컴파일러가 두 가지 호환되지 않는 유형을 혼합 할 수없는 것처럼 포인터에서 작동하고 있음을 프로그래머가 알도록합니다.
참고 URL : https://stackoverflow.com/questions/13366083/why-does-the-arrow-operator-in-c-exist
'Programing' 카테고리의 다른 글
Javascript가 프로토 타입 기반 언어라는 것은 무엇을 의미합니까? (0) | 2020.04.07 |
---|---|
iOS 시뮬레이터 스크린 샷은 어디에 저장됩니까? (0) | 2020.04.07 |
CustomErrors mode =“끄기” (0) | 2020.04.07 |
다른 곳에 YAML 파일을 어떻게 포함시킬 수 있습니까? (0) | 2020.04.07 |
선과 가로 축 사이의 각도를 계산하는 방법은 무엇입니까? (0) | 2020.04.07 |