배열, 포인터 및 함수에 대한 C 구문이 이런 식으로 설계된 이유는 무엇입니까?


16

비슷한 질문을 많이 본 후에는

int (*f)(int (*a)[5])C의 의미는 무엇입니까 ?

사람들이 C 구문을 이해하는 데 도움이 되는 프로그램 을 만들었을 때조차도 도움이 될 수는 없지만 궁금합니다.

C의 구문이 이런 식으로 설계된 이유는 무엇입니까?

예를 들어, 포인터를 디자인하는 경우 "10 요소 배열의 포인터에 대한 포인터"를 다음과 같이 변환합니다.

int*[10]* p;

그리고 아닙니다

int* (*p)[10];

대부분의 사람들이 동의 할 것이라고 생각합니다.

왜 직관적이지 않은 구문인지 궁금합니다. 내가 알지 못하는 구문으로 해결할 특정 문제가 있었습니까 (모호한가?)?


2
당신은 이것에 대한 진정한 답이 없다는 것을 알고 있습니다. 권리? 당신이 얻는 것은 추측 일뿐입니다.
BЈовић

7
@VJo- "실제"(즉, 객관적인) 답변이있을 수 있습니다. 언어 작성자와 표준위원회 모두 이러한 결정 중 다수를 명시 적으로 정당화 (또는 적어도 설명)했습니다.
detly

제안한 구문이 C 구문보다 "직관적"이라고 생각하지는 않습니다. C는 무엇인가; 일단 배운 후에는 이러한 질문이 다시는 없을 것입니다. 당신이 그것을 배우지 않았다면 ... 글쎄, 그것은 진짜 문제 일 것입니다.
Caleb

1
@Caleb : 내가 그것을 배웠고 여전히이 질문이 있었기 때문에 그렇게 쉽게 결론을 내린 방법이 재미있다 ...
user541686

1
cdecl명령은 복잡한 C 선언을 디코딩하는 데 매우 유용합니다. cdecl.org 에는 웹 인터페이스도 있습니다 .
Keith Thompson

답변:


16

그것의 역사에 대한 나의 이해는 그것이 두 가지 요점을 기반으로한다는 것입니다 ...

첫째, 언어 작성자는 구문을 유형 중심이 아닌 변수 중심으로 만드는 것을 선호했습니다. 즉 그들이 선언에서보기에 프로그래머를 원하고 생각입니다 "나는 표현을 쓰는 경우 *func(arg), 그이 될 것입니다 int, 내가 쓰면 *arg[N]내가 float를해야합니다" "보다는 func기능 복용에 대한 포인터해야 그리고 "를 반환 합니다 .

WikipediaC 항목은 다음과 같이 주장합니다.

Ritchie의 아이디어는 사용과 비슷한 맥락에서 식별자를 선언하는 것이 었습니다. "선언은 사용을 반영합니다".

... 아마도, 나는 당신을 위해 확장 된 견적을 찾기 위해 손을 댈 필요가없는 K & R2의 p122 인용.

둘째, 임의의 간접적 수준의 처리를 할 때 일관된 선언 구문을 생각해내는 것은 실제로 정말 정말 어렵습니다. 귀하의 예는 즉시 생각한 유형을 표현하는 데 효과적 일 수 있지만 해당 유형의 배열에 대한 포인터를 가져 와서 다른 끔찍한 혼란을 반환하는 함수로 확장됩니까? (아마도 확인 했습니까? 증명할 수 있습니까? ).

C의 성공의 일부는 컴파일러가 다양한 플랫폼 용으로 작성 되었기 때문에 컴파일러를보다 쉽게 ​​작성할 수 있도록하기 위해 어느 정도의 가독성을 무시하는 것이 좋을 수 있습니다.

나는 언어 문법이나 컴파일러 작성 전문가가 아닙니다. 그러나 나는 알아야 할 것이 많다는 것을 충분히 알고 있습니다.)


2
"컴파일러를보다 쉽게 ​​작성할 수 있도록"... C는 구문 분석하기 어려운 것으로 악명이 높다 (C ++ 만 탑니다).
Jan Hudec

1
@ JanHudec-글쎄 ... 그래. 그것은 수밀 진술이 아닙니다. 그러나 C는 문맥이없는 문법으로 구문 분석 할 수 없지만, 한 사람이 구문 분석 할 방법을 찾게되면 어려운 단계가되지 않습니다. 사실, 사람들이 컴파일러를 쉽게 사용할 수 있기 때문에 초기 에는 많은 것이 있었기 때문에 K & R은 약간의 균형을 맞췄을 것입니다. (Richard Gabriel의 악명 높은 "Worse is Better"의 상승에서 , 그는 새로운 플랫폼을위한 C 컴파일러를 작성하는 것이 쉽다는 사실을
당연한 것으로 여기고있다

그건 그렇고이 문제를 해결하게되어 기쁩니다. 파싱과 문법에 대해서는 잘 모릅니다. 나는 역사적 사실로부터 더 많은 추론을 할 것입니다.
detly

12

C 언어의 많은 이상한 점은 컴퓨터를 설계했을 때의 작동 방식으로 설명 할 수 있습니다. 스토리지 메모리는 매우 제한되어 있으므로 소스 코드 파일 자체 의 크기를 최소화하는 것이 매우 중요 했습니다. 70 년대와 80 년대의 프로그래밍 실습은 소스 코드에 가능한 한 적은 문자를 포함시키는 것이 좋으며 과도한 소스 코드 주석은 포함하지 않는 것이 좋습니다.

하드 드라이브에 거의 무제한의 저장 공간이있는 오늘날의 말도 안됩니다. 그러나 이것이 C가 일반적으로 이상한 구문을 갖는 이유의 일부입니다.


배열 포인터에 관해서는 두 번째 예가 있어야합니다 int (*p)[10];(구문은 매우 혼란 스럽습니다). 아마 "10의 배열을 가리키는 포인터"로 읽었을 것입니다. 괄호가 아닌 경우 컴파일러는이를 10 개의 포인터 배열로 해석하여 선언에 완전히 다른 의미를 부여합니다.

배열 포인터와 함수 포인터는 모두 C에서 매우 모호한 구문을 가지고 있기 때문에 현명한 일은 이상한 점을 typedef하는 것입니다. 아마도 이것처럼 :

모호한 예 :

int func (int (*arr_ptr)[10])
{
  return 0;
}

int main()
{
  int array[10];
  int (*arr_ptr)[10]  = &array;
  int (*func_ptr)(int(*)[10]) = &func;

  func_ptr(arr_ptr);
}

명확하지 않은 동등한 예 :

typedef int array_t[10];
typedef int (*funcptr_t)(array_t*);


int func (array_t* arr_ptr)
{
  return 0;
}

int main()
{
  int        array[10];
  array_t*   arr_ptr  = &array; /* non-obscure array pointer */
  funcptr_t  func_ptr = &func;  /* non-obscure function pointer */

  func_ptr(arr_ptr);
}

함수 포인터의 배열을 다루는 경우 상황이 더 모호해질 수 있습니다. 또는 가장 모호한 것 : 함수 포인터를 반환하는 함수 (매우 유용함). 그러한 것들에 typedef를 사용하지 않으면 빨리 미치게됩니다.


아, 마침내 합리적인 답변. :-) 특정 구문이 실제로 소스 코드 크기를 줄이는 방법에 대해 궁금하지만 어쨌든 그것은 그럴듯한 아이디어이며 의미가 있습니다. 감사. +1
541686

소스 코드 크기와 컴파일러 작성에 관한 것이 아니라 "typdef away the oddness"에 대해 +1이라고 말하고 싶습니다. 내가 할 수 있다는 것을 깨달았을 때 정신 건강이 크게 향상되었습니다.
detly

2
소스 코드 크기에 대한 [인용 필요]. 나는 그런 제한에 대해 들어 본 적이 없다. (아마도 "모두가 알고있는"것일 것이다.)
Sean McMillan

1
IBM, DEC 및 XEROX 키트에서 COBOL, Assembler, CORAL 및 PL / 1로 70 년대에 프로그램을 코딩했으며 소스 코드 크기 제한을 결코 경험하지 못했습니다. 배열 크기, 실행 가능 크기, 프로그램 이름 크기에 대한 제한은 있지만 소스 코드 크기는 절대 아닙니다.
James Anderson

1
@Sean McMillan : 소스 코드 크기가 제한적이라고 생각하지 않습니다 (그 당시 Pascal과 같은 장황한 언어는 꽤 인기가있었습니다). 이 경우에도 소스 코드를 미리 구문 분석하고 긴 키워드를 짧은 1 바이트 코드 (예 : 일부 기본 통역사)로 바꾸는 것이 매우 쉬울 것이라고 생각합니다. 그래서 "메모리가 부족한 기간에 발명 되었기 때문에 C는 간결합니다."라는 주장은 다소 약합니다.
Giorgio

7

꽤 간단합니다. int int *p임을 의미합니다 *p. int a[5]그것은 a[i]int 임을 의미합니다 .

int (*f)(int (*a)[5])

*f함수 라는 의미 *a는 5 개의 정수 배열이므로 5 개의 정수 배열에 f대한 포인터를 가져 와서 int를 반환하는 함수입니다. 그러나 C에서는 배열에 포인터를 전달하는 것이 유용하지 않습니다.

C 선언은 이것을 복잡하게 만드는 경우가 거의 없습니다.

또한 typedef를 사용하여 명확히 할 수 있습니다.

typedef int vec5[5];
int (*f)(vec5 *a);

4
이것이 무례하게 들리면 사과하지만 (문제는 아닙니다) 질문의 요점을 놓친 것 같습니다 ... : \
user541686

2
@Mehrdad : Kernighan과 Ritchie의 마음에 무엇이 있는지 말할 수 없습니다. 나는 구문의 논리를 설명했다. 나는 대부분의 사람들에 대해 모른다. 그러나 나는 당신의 제안 된 구문이 더 명확하다고 생각하지 않는다.
케빈 클라인

동의합니다. 그렇게 복잡한 선언을 보는 것은 드문 일입니다.
Caleb

C의 디자인은 선행을 선언의 typedef, const, volatile, 선언 내에서 일을 초기화하고 능력. 선언 구문에 대한 성가신 애매 모호함 (예 : 유형 또는 선언에 int const *p, *q;바인딩 해야하는지 여부 const)은 원래 설계된 언어에서 발생할 수 없었습니다. 언어가 형식과 선언 사이에 콜론을 추가하기를 원했지만 한정자가없는 내장 "예약어"형식을 사용할 때 생략이 허용되었습니다. 다음은의 의미 int: const *p,*q;int const *: p,*q;명확 것이다.
supercat

3

변수에 첨부 된 연산자로 * []를 고려해야한다고 생각합니다. *는 변수 [] 뒤에 씁니다.

타입 표현식을 읽자

int* (*p)[10];

가장 안쪽 요소는 변수 p 인 p

p

의미 : p는 변수입니다.

변수 앞에 *가 있기 전에 * 연산자는 항상 참조하는 표현식 앞에 놓입니다.

(*p)

의미 : 변수 p는 포인터입니다. ()없이 오른쪽에있는 [] 연산자가 우선 순위가 높습니다. 즉

**p[]

로 구문 분석됩니다

*(*(p[]))

다음 단계는 []입니다. 더 이상 ()이 없으므로 []는 바깥 *보다 우선 순위가 높습니다. 따라서

(*p)[]

의미 : (변수 p는 포인터) 배열에 대한. 그런 다음 두 번째 * :

* (*p)[]

의미 : 포인터의 ((변수 p는 포인터) 배열)

마지막으로 int 연산자 (유형 이름)가 가장 낮은 우선 순위를 갖습니다.

int* (*p)[]

(정수의 ((변수 p는 포인터)) 포인터를 의미합니다.

따라서 전체 시스템은 연산자를 사용한 유형 표현식을 기반으로하며 각 연산자에는 고유 한 우선 순위 규칙이 있습니다. 이를 통해 매우 복잡한 유형을 정의 할 수 있습니다.


0

생각하기 시작할 때 그리 어렵지 않으며 C는 결코 쉬운 언어가 아닙니다. 그리고 int*[10]* p실제로는 쉽지 않습니다. int* (*p)[10] 어떤 유형의 k가int*[10]* p, k;


2
k는 실패한 코드 검토 일 것이다. 나는 컴파일러가 무엇을 할 것인지, 심지어
귀찮을

그리고 k는 왜 코드 검토에 실패했을까요?
Dainius

1
코드를 읽을 수없고 유지할 수 없기 때문입니다. 코드는 수정하기에 정확하지 않으며, 분명히 정확하며 유지 보수를 통해 올바른 상태를 유지할 수 있습니다. k 타입이 무엇인지 묻어 야한다는 사실은 코드가 이러한 기본 요구 사항을 충족시키지 못한다는 신호입니다.
mattnz

1
본질적으로 같은 행에 다른 유형의 변수 선언이 3 개 있습니다 (예 : int * p, int i [10] 및 int k). 용납 할 수 없습니다. 변수가 int width, height, depth; 등의 관계 형식을 갖는 경우 동일한 유형의 여러 선언이 허용됩니다. 많은 사람들이 int * p를 사용하여 프로그램한다는 것을 명심하십시오. 그래서 'int * p, i;'에서 i는 무엇입니까?
mattnz

1
@mattnz가 말하려는 것은 원하는만큼 영리 할 수 ​​있지만 의도가 분명하지 않거나 코드가 잘못 작성 / 읽을 수없는 경우 모두 의미가 없습니다. 이런 종류의 물건은 종종 코드가 깨지고 시간을 낭비합니다. 또한, pointer to intint같은 종류는, 그래서 그들은 개별적으로 선언해야되지 않습니다. 기간. 남자의 말을 듣습니다. 그는 18k의 담당자가 있습니다.
Braden Best
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.