C에 화살표 (->) 연산자가 존재하는 이유는 무엇입니까?


264

도트 ( .) 연산자는 구조체 ->의 멤버에 액세스하는 데 사용되고 C 의 화살표 연산자 ( )는 해당 포인터가 참조하는 구조체의 멤버에 액세스하는 데 사용됩니다.

포인터 자체에는 도트 연산자로 액세스 할 수있는 멤버가 없습니다 (실제로는 가상 메모리의 위치를 ​​설명하는 숫자이므로 멤버가 없습니다). 따라서 포인터 (포인팅 시간 afaik에서 컴파일러에 알려진 정보)에서 포인터를 사용하는 경우 도트 연산자를 자동으로 역 참조하도록 점 연산자를 정의한 경우 모호성이 없습니다.

그렇다면 언어 제작자들이 왜 이처럼 불필요한 연산자를 추가하여 일을 더 복잡하게하기로 결정 했습니까? 큰 디자인 결정은 무엇입니까?


1
관련 : stackoverflow.com/questions/221346/…- 또한 무시할 수 있습니다->
Krease

16
@Chris 그것은 C ++에 관한 것입니다. 물론 큰 차이가 있습니다. 그러나 C가 이런 식으로 설계된 이유 에 대해 이야기하고 있으므로 C ++이 존재하기 전에 1970 년대에 돌아 온다고 가정 해 봅시다.
Mysticial

5
가장 좋은 추측은 화살표 연산자가 시각적으로 "그것을보고 있습니다. 당신은 여기에서 포인터를 다루고 있습니다"
Chris

4
한눈 에이 질문은 매우 이상하다고 생각합니다. 모든 것이 신중하게 설계된 것은 아닙니다. 당신이 평생이 스타일을 유지한다면, 당신의 세계는 질문으로 가득 차있을 것입니다. 가장 많은 표를 얻은 답변은 실제로 유익하고 명확합니다. 그러나 그것은 당신의 질문의 요점에 맞지 않습니다. 당신의 질문의 스타일을 따르십시오, 나는 너무 많은 질문을 할 수 있습니다. 예를 들어, 키워드 'int'는 '정수'의 약어입니다. 키워드 'double'도 짧지 않은 이유는 무엇입니까?
junwanghe

1
@junwanghe이 질문은 실제로 유효한 우려를 나타냅니다. 왜 .연산자가 연산자보다 우선 순위가 높 *습니까? 그렇지 않은 경우 * ptr.member 및 var.member를 가질 수 있습니다.
milleniumbug

답변:


358

나는 당신의 질문을 두 가지 질문으로 해석 할 것입니다 : 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및 결과 주소 42int값 할당 "으로 해석되었습니다 . 즉, 위의 주소 값에 할당 42됩니다 . 이 사용은 왼쪽의 표현 유형에 신경 쓰지 않았습니다. 왼쪽은 rvalue 숫자 주소 (포인터 또는 정수)로 해석되었습니다.int7->

속임수 이런 종류의 불가능했다 *.조합. 넌 못해

(*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라는 것입니다. 일종의 쓰기 가능한 메모리 블록입니다.cstruct Tbcc

이 작업을 수행 한 경우

S *s;
...
s.b = 42;

코드는 유효한 것으로 간주되며 ( slvalue 이기 때문에 ) 컴파일러는 바이트 오프셋 2에서 포인터 s자체에 데이터를 쓰려고 시도 합니다. 그런 문제에 대해서는 관심이 없었습니다.

즉, 해당 언어 버전 .에서 포인터 유형의 연산자 오버로드에 대한 제안 아이디어가 작동하지 않습니다. 연산자 .는 포인터와 함께 사용할 때 (lvalue 포인터 또는 lvalue가 전혀 없음) 이미 매우 특정한 의미를가집니다. 의심 할 여지없이 매우 이상한 기능이었습니다. 그러나 당시에는 그곳에있었습니다.

물론,이 이상한 기능은 .C-K & R C의 재 작업 된 버전에서 포인터 에 대해 오버로드 된 연산자 (제안한대로)를 도입하는 것에 대한 강력한 이유 가 아닙니다. 그러나 아직 완료되지 않았습니다. 그 당시에는 CRM 버전 C로 작성된 일부 레거시 코드가 지원되어야했습니다.

(1975 C 참조 매뉴얼의 URL이 안정적이지 않을 수 있습니다. 약간의 차이가있는 다른 사본이 여기 있습니다 .)


10
인용 된 C 참조 매뉴얼의 7.1.8 절에 "E1이 포인터 유형이어야한다는 요구 사항을 완화하는 것을 제외하고 ''E1-> MOS ''표현은 ''(* E1). '. "
키이스 톰슨

1
*i주소 5에서 왜 기본 유형 (int?)의 lvalue 가 아니 었 습니까? 그러면 (* i) .b는 같은 방식으로 작동했을 것입니다.
Random832

5
@ 레오 : 글쎄, 어떤 사람들은 C 언어를 고급 어셈블러처럼 좋아합니다. C 역사에서 그 기간에 언어는 실제로 더 높은 수준의 어셈블러였습니다.
AnT

29
허. 따라서 이것은 UNIX의 많은 구조 (예 struct stat:)가 필드 앞에 접두사를 붙인 이유를 설명합니다 st_mode.
icktoofay

5
@ perfectionm1ng : bell-labs.com이 Alcatel-Lucent에 의해 인계되어 원본 페이지가 사라진 것 같습니다. 다른 사이트에 대한 링크를 업데이트했지만 해당 사이트가 얼마나 오래 지속되는지는 알 수 없습니다. 어쨌든 "ritchie c reference manual"에 대한 인터넷 검색은 일반적으로 문서를 찾습니다.
AnT

46

역사적 (좋은 이미보고 된) 이유 외에도 연산자 우선 순위에는 약간의 문제가 있습니다. 점 연산자는 스타 연산자보다 우선 순위가 높습니다. 따라서 포인터 포인터를 포함하는 구조체에 포인터를 포함하는 구조체가 있으면 구조체와 포인터가 있습니다 ...이 두 가지는 동일합니다.

(*(*(*a).b).c).d

a->b->c->d

그러나 두 번째는 분명히 더 읽기 쉽습니다. 화살표 연산자는 우선 순위가 가장 높으며 (점과 동일) 왼쪽에서 오른쪽으로 연결됩니다. 필자는 구조체와 구조체에 대한 포인터에 도트 연산자를 사용하는 것보다 분명하다고 생각합니다. 왜냐하면 선언에서 보지 않아도 표현식의 유형을 알기 때문에 다른 파일에있을 수 있기 때문입니다.


2
구조체와 구조체에 대한 포인터를 모두 포함하는 중첩 된 데이터 형식을 사용하면 각 하위 멤버 액세스에 적합한 연산자를 선택하는 것에 대해 생각해야하기 때문에 상황이 더 어려워 질 수 있습니다. ab-> c-> d 또는 a-> bc-> d로 끝날 수 있습니다 (자유형 라이브러리를 사용할 때이 문제가 발생했습니다-항상 소스 코드를 찾아야했습니다). 또한 포인터를 처리 할 때 컴파일러가 포인터를 자동으로 역 참조 할 수없는 이유를 설명하지 않습니다.
Askaga

3
당신이 말하는 사실은 정확하지만 어떤 식 으로든 내 원래의 질문에 대답하지 않습니다. a->와 * (a)의 동등성을 설명합니다. 표기법 (다른 질문에서 이미 여러 번 설명 되었음)은 물론 언어 설계에 대한 모호한 표현을 제공합니다. 귀하의 답변이 그다지 도움이되지 않았으므로 공감대를 찾지 못했습니다.
Askaga

16
@effeffe, 영업 이익은 언어가 쉽게 해석 한 수 있다고 말하는 a.b.c.d(*(*(*a).b).c).d렌더링, ->쓸모없는 연산자를. 따라서 OP 버전 ( a.b.c.d)은 (와 비교하여 a->b->c->d) 동일하게 읽을 수 있습니다. 그렇기 때문에 귀하의 답변이 OP의 질문에 답변하지 않습니다.
Shahbaz 2016 년

4
즉 자바 프로그래머의 경우 일 수있다 @Shahbaz하는 C / C ++ 프로그래머가 이해 a.b.c.d하고 a->b->c->d두 같이 매우 상이한 것들 먼저 중첩 된 서브 - 객체에 대한 단일 메모리 액세스이다 (이 경우 단지 하나의 메모리 오브젝트가 ), 두 번째는 세 개의 메모리 액세스로, 네 개의 별개의 객체를 통해 포인터를 쫓습니다. 그것은 메모리 레이아웃의 큰 차이이며, C 가이 두 경우를 매우 눈에 띄게 구별하는 것이 옳다고 생각합니다.
cmaster-monica reinstate

2
@Shahbaz 나는 자바 프로그래머의 모욕으로, 완전히 암시 적 포인터를 가진 언어에 단순히 익숙하다는 것을 의미하지는 않았다. Java 프로그래머로 자랐다면 같은 방식으로 생각할 것입니다 ... 어쨌든, 실제로 C에서 보는 연산자 오버로드가 최적이 아닌 것으로 생각합니다. 그러나 나는 우리 모두가 거의 모든 것에 대해 연산자를 자유롭게 오버로드하는 수학자에 의해 망쳐 졌다는 것을 인정합니다. 또한 사용 가능한 기호 세트가 다소 제한되어 있기 때문에 그들의 동기를 이해합니다. 결국, 그것은 당신이 선을 그리는 곳의 문제 일 것입니다 ...
cmaster-reinstate monica

19

C는 또한 모호한 것을 만들지 않는 데 능숙합니다.

점이 과부하되어 두 가지를 모두 의미 할 수는 있지만 화살표는 컴파일러가 두 가지 호환되지 않는 유형을 혼합 할 수없는 것처럼 포인터에서 작동하고 있음을 프로그래머가 알도록합니다.


4
이것은 간단하고 정답입니다. C는 주로 IMO C.에 대한 최고의 것들 중 하나입니다 피하기 오버로드를 시도
jforberg

10
C의 많은 것들이 모호하고 애매합니다. 암시 적 유형 변환이 있으며 수학 연산자가 오버로드되고 체인 인덱싱은 다차원 배열 또는 포인터 배열을 인덱싱하고 무엇이든 숨기는 매크로 일 수 있는지 여부에 따라 완전히 다르게 작동합니다 (대문자 명명 규칙은 도움이되지만 C는 ' 티).
PSkocik
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.