배열의 경우 왜 a [5] == 5 [a]입니까?


1621

Joel 이 C 프로그래밍 언어 (일명 : K & R)의 스택 오버플로 팟 캐스트 # 34 에서 지적한 바와 같이 C 에 배열의이 속성에 대한 언급이 있습니다.a[5] == 5[a]

Joel은 포인터 산술로 인한 것이지만 여전히 이해하지 못한다고 말합니다. 왜 그렇a[5] == 5[a] 습니까?


48
a [+]와 같은 것이 * (a ++) OR * (++ a)와 같이 작동합니까?
Egon

45
@ Egon : 매우 독창적이지만 불행히도 컴파일러는 작동하지 않습니다. 컴파일러는 a[1]문자열이 아닌 일련의 토큰으로 해석합니다 . * ({정수 위치} a {operator} + {정수} 1)는 * ({integer} 1 {operator} + {정수 위치} a)와 같습니다. * ({정수}} {정수} + {정수}}의 정수 위치
Dinah

11
이에 대한 흥미로운 화합물 변동에 도시 비논리적 배열 액세스 가있는 경우, char bar[]; int foo[];그리고 foo[i][bar]식으로 사용된다.
Jonathan Leffler

5
@ EldritchConundrum, 왜 '컴파일러가 왼쪽 부분이 포인터인지 확인할 수 없다'고 생각하십니까? 예, 그럴 수 있습니다. 주어진 및에 대해 a[b]= *(a + b)는 사실 이지만 모든 유형에 대해 정식으로 정의되는 것은 언어 디자이너의 자유로운 선택이었습니다 . 그들이 허용 하는 동안 그들이 금지 하는 것을 막을 수있는 것은 없습니다 . ab+i + pp + i
ach

13
@Andrey One은 일반적 +으로 정류 할 것으로 예상 하므로 실제 문제는 별도의 오프셋 연산자를 디자인하는 대신 포인터 연산을 산술과 유사하게 만드는 것입니다.
Eldritch Conundrum

답변:


1924

C 표준은 []연산자를 다음과 같이 정의합니다 .

a[b] == *(a + b)

따라서 다음과 같이 a[5]평가됩니다.

*(a + 5)

다음으로 5[a]평가됩니다.

*(5 + a)

a배열의 첫 번째 요소에 대한 포인터입니다. a[5](5 개)의 값입니다 요소 에서 추가 a와 같은 인 *(a + 5), 초등학교 수학에서 우리가 그가 (또한이 동일 알고는 교환 법칙이 성립 ).


325
* ((5 * sizeof (a)) + a)와 비슷하지 않은지 궁금합니다. 그래도 훌륭한 설명입니다.
John MacIntyre

92
@ Dinah : C 컴파일러 관점에서 봤습니다. sizeof가 필요하지 않으며 내가 언급 한 표현은 동일합니다. 그러나 머신 코드 생성시 컴파일러는 sizeof를 고려합니다. A는 int 배열 인 경우 a[5]처럼 뭔가 컴파일 mov eax, [ebx+20]대신[ebx+5]
메흐 다드 Afshari

12
@Dinah : A는 0x1230과 같은 주소입니다. a가 32 비트 int 배열에 있으면 a [0]은 0x1230, a [1]은 0x1234, a [2]는 0x1238 ... a [5]는 x1244 등입니다. 0x1230, 우리는 0x1235를 얻습니다.
James Curran

36
@ sr105 : 이것은 피연산자 중 하나가 포인터이고 다른 하나는 정수인 + 연산자의 특별한 경우입니다. 표준에 따르면 결과는 포인터 유형이 될 것입니다. 컴파일러는 충분히 똑똑해야합니다.
aib December

48
"초등학교 수학에서 우리가 그이 동일 알고있다"- 당신이 단순화되는 것을 이해하지만, 나는이되어 기분이 사람들과 함께 있어요 이상 단순화. 초등이 아닙니다 *(10 + (int *)13) != *((int *)10 + 13). 다시 말해, 여기에는 초등학교 산술보다 더 많은 것들이 있습니다. commutativity는 어떤 피연산자가 포인터인지 (그리고 객체의 크기) 인식하는 컴파일러에 결정적으로 의존합니다. 그것을 다른 방법을 넣어하려면 (1 apple + 2 oranges) = (2 oranges + 1 apple),하지만 (1 apple + 2 oranges) != (1 orange + 2 apples).
LarsH

288

배열 액세스는 포인터 측면에서 정의되기 때문입니다. a[i]는 의미있는 것으로 정의되며 *(a + i), 이는 정류 적입니다.


42
배열은 포인터로 정의되어 있지 않지만 액세스 는 있습니다.
궤도에서 가벼운 레이스

5
" *(i + a)로 작성 될 수있는 i[a]" 와 동일합니다 "를 추가 합니다.
Jim Balter

4
6.5.2.1 : 2 접미사 식과 대괄호로 묶인 표현식 []은 배열 개체의 요소를 첨자로 지정한 것입니다. 아래 첨자 연산자 []의 정의는 E1 [E2]가 (* ((E1) + (E2)))와 동일하다는 것입니다. 이진 + 연산자에 적용되는 변환 규칙으로 인해 E1이 배열 개체 (동일하게 배열 개체의 초기 요소에 대한 포인터)이고 E2가 정수이면 E1 [E2]는 E2 번째 요소를 지정합니다. E1 (0부터 계산)
Vality

더 정확하려면 : 배열에 액세스하면 배열이 붕괴됩니다.
12431234123412341234123 16:14에

Nitpick : " *(a + i)정류 적" 이라는 말은 의미가 없습니다 . 그러나 덧셈 은 교환 형 *(a + i) = *(i + a) = i[a]이기 때문 입니다.
Andreas Rejbrand

231

다른 답변에서 무언가를 놓치고 있다고 생각합니다.

그렇습니다. p[i]정의는이며 *(p+i), 덧셈은 덧셈이되므로 *(i+p)이며, []연산자 의 정의에 따라 동일합니다 i[p].

(에서 array[i]배열 이름은 암시 적으로 배열의 첫 번째 요소에 대한 포인터로 변환됩니다.)

그러나 더하기의 commutativity가이 경우에 명백한 것은 아닙니다.

두 피연산자가 같은 유형이거나 공통 유형으로 승격 된 다른 숫자 유형 인 경우, commutativity는 다음과 같이 완벽하게 이해 x + y == y + x됩니다.

그러나이 경우 포인터 연산에 대해 구체적으로 이야기하고 있습니다. 한 피연산자는 포인터이고 다른 피연산자는 정수입니다. (정수 + 정수는 다른 연산이며 포인터 + 포인터는 의미가 없습니다.)

C 표준의 +연산자 ( N1570 6.5.6)에 대한 설명 은 다음과 같습니다.

또한 두 피연산자 모두 산술 유형을 갖거나 한 피연산자는 완전한 객체 유형에 대한 포인터이고 다른 피연산자는 정수 유형이어야합니다.

그것은 쉽게 말할 수 있습니다 :

또한 두 피연산자 모두 산술 유형을 가지거나 왼쪽 피연산자는 완전한 객체 유형에 대한 포인터이고 오른쪽 피연산자 는 정수 유형이어야합니다.

이 경우 모두 i + pi[p]불법이 될 것입니다.

C ++ 용어로, 우리는 실제로 +다음과 같이 느슨하게 설명 될 수있는 두 세트의 오버로드 연산자를 가지고 있습니다.

pointer operator+(pointer p, integer i);

pointer operator+(integer i, pointer p);

그중 첫 번째 만이 실제로 필요합니다.

왜 이런 식입니까?

C ++은 B (배열 인덱스의 교환 법칙이 명시 1972에서 설명한에서있어 C에서이 정의 유전 B에 대한 사용자의 참조 에서있어) BCPL 아니라 짝수로를 받고있다 (수동 일자 1967), 이전 언어 (CPL? Algol?).

따라서 배열 인덱싱은 덧셈의 관점에서 정의되고, 포인터와 정수의 덧셈도 계산적이며 수십 년 전 C의 조상 언어로 거슬러 올라갑니다.

이 언어들은 현대 C보다 훨씬 덜 타이핑되었습니다. 특히, 포인터와 정수의 구별은 종종 무시되었습니다. (초기 C 프로그래머들은 unsigned키워드를 언어에 추가 하기 전에 포인터를 부호없는 정수로 사용하기도했습니다 .) 따라서 피연산자가 다른 유형이기 때문에 비정규 식을 추가하는 아이디어는 해당 언어의 디자이너에게는 발생하지 않았을 것입니다. 사용자가 정수, 포인터 또는 그 밖의 다른 것을 포함하여 두 개의 "사물"을 추가하고자한다면 언어에 의존하지 않았습니다.

그리고 수년에 걸쳐이 규칙을 변경하면 기존 코드가 손상되었을 수 있습니다 (1989 ANSI C 표준이 좋은 기회 였을 수도 있음).

포인터를 왼쪽에 놓고 정수를 오른쪽에 놓도록 C 및 / 또는 C ++를 변경하면 일부 기존 코드가 손상 될 수 있지만 실제 표현력이 손실되지는 않습니다.

그래서 지금 우리가 arr[3]하고 3[arr]후자의 형태가 바깥에 표시해서는 안하지만, 정확히 같은 일을 의미 IOCCC .


12
이 속성에 대한 환상적인 설명. 높은 수준의 관점에서 볼 때, 나는 3[arr]흥미로운 인공물 이라고 생각 하지만 거의 사용되지는 않을 것입니다. 내가 잠시 뒤로 물었던 이 질문 (< stackoverflow.com/q/1390365/356> )에 대한 대답 은 구문에 대한 생각 방식을 변경했습니다. 기술적으로 이러한 일을하는 옳고 그른 방법은 없지만, 이러한 종류의 기능은 구현 세부 사항과 분리 된 방식으로 생각하기 시작합니다. 구현 세부 사항을 고치면 부분적으로 손실되는 이러한 다른 사고 방식에 이점이 있습니다.
Dinah

3
덧셈은 교환이다. C 표준의 경우 그것을 정의하는 것이 이상 할 것입니다. 그렇기 때문에 "두 피연산자 모두 산술 유형을 갖거나 왼쪽 피연산자가 완전한 객체 유형을 가리키는 포인터이고 오른쪽 피연산자가 정수 유형을 가져야합니다." -물건을 추가하는 대부분의 사람들에게는 이치에 맞지 않습니다.
iheanyi

9
@iheanyi : 덧셈은 일반적으로 정류 적이며 일반적으로 같은 유형의 두 피연산자가 필요합니다. 포인터 추가를 사용하면 포인터와 정수를 추가 할 수 있지만 두 개의 포인터는 추가 할 수 없습니다. IMHO는 이미 포인터가 왼쪽 피연산자가되어야하는 것은 충분히 이상한 특수 사례이므로 큰 부담이되지 않습니다. (일부 언어는 문자열 연결에 "+"를 사용합니다. 확실히 정답은 아닙니다.)
Keith Thompson

3
@ supercat, 그게 더 나빠. 그것은 때때로 x + 1! = 1 + x를 의미합니다. 그것은 첨가의 연관성을 완전히 위반할 것입니다.
iheanyi

3
@iheanyi : 나는 당신이 commutative property를 의미한다고 생각합니다; 대부분의 구현에서 (1LL + 1U) -2! = 1LL + (1U-2)이기 때문에 덧셈은 이미 연관성이 없습니다. 실제로,이 변경은 현재와는 다른 일부 상황을 연관시킬 수 있습니다. 예를 들어 3U + (UINT_MAX-2L)는 (3U + UINT_MAX) -2와 같습니다. 그러나 가장 좋은 것은 언어가 승격 가능한 정수와 "래핑"대수 고리에 새로운 고유 한 유형을 추가하여 ring16_t65535를 보유하고 있는에 2를 추가 하면의 크기와 상관없이ring16_t 값이 1 인을int 생성하는 것 입니다 .
supercat

196

그리고 물론

 ("ABCD"[2] == 2["ABCD"]) && (2["ABCD"] == 'C') && ("ABCD"[2] == 'C')

이것의 주된 이유는 C가 설계되었을 때 70 년대에 컴퓨터에 많은 메모리가 없었기 때문에 (64KB는 많았으므로) C 컴파일러는 구문 검사를 많이하지 않았기 때문입니다. 따라서 " X[Y]"은 (는) " *(X+Y)"

" +="및 " ++"구문 도 설명합니다 . " A = B + C" 형식의 모든 항목 은 동일한 컴파일 된 형식을가집니다. 그러나 B가 A와 동일한 객체 인 경우 어셈블리 수준 최적화를 사용할 수 있습니다. 그러나 컴파일러는 그것을 인식하기에 충분히 밝지 않았으므로 개발자는 ( A += C)해야했습니다. 마찬가지로 Cwas 1인 경우 다른 어셈블리 수준 최적화를 사용할 수 있었으며 컴파일러는이를 인식하지 못했기 때문에 개발자가 명시 적으로 만들어야했습니다. (더 최근에는 컴파일러가 사용하므로 요즘 구문은 거의 필요하지 않습니다)


127
실제로, 그것은 거짓으로 평가됩니다. 첫 번째 용어 "ABCD"[2] == 2 [ "ABCD"]는 true 또는 1로 평가되며 1! = 'C': D
Jonathan Leffler

8
@Jonathan : 동일한 모호성으로 인해이 게시물의 원래 제목을 편집 할 수 있습니다. 우리는 등가 수학 수학, 코드 구문 또는 의사 코드입니다. 나는 수학적 동등성을 주장하지만 코드에 관해 이야기하고 있기 때문에 코드 구문 측면에서 모든 것을보고 있다는 것을 피할 수 없습니다.
Dinah

19
이것은 신화가 아닌가? 컴파일러를 단순화하기 위해 + = 및 ++ 연산자가 생성되었음을 의미합니까? 일부 코드는 더 명확 해지며 컴파일러에서 수행하는 작업에 관계없이 유용한 구문이됩니다.
Thomas Padron-McCarthy

6
+ =와 ++는 또 다른 중요한 이점이 있습니다. 평가하는 동안 왼쪽에서 일부 변수가 변경되면 변경은 한 번만 수행됩니다. a = a + ...; 두 번 할 것입니다.
Johannes Schaub-litb

8
아니오- "ABCD"[2] == * ( "ABCD"+ 2) = * ( "CD") = 'C'. 문자열을 역 참조하면 부분 문자열이 아닌 문자가 표시됩니다.
MSalters

55

Dinah의 문제에 대해 아무도 언급하지 않은 것 sizeof:

포인터에는 정수만 추가 할 수 있으며 두 개의 포인터를 함께 추가 할 수는 없습니다. 이렇게하면 정수에 포인터를, 포인터에 정수를 추가 할 때 컴파일러는 크기를 고려해야하는 비트를 항상 알고 있습니다.


1
허용 된 답변의 의견에서 이것에 대해 상당히 철저한 대화가 있습니다. 나는 원래 질문에 대한 편집에서 대화를 언급했지만 sizeof에 대한 귀하의 매우 유효한 우려를 직접적으로 다루지는 않았습니다. SO에서 가장 잘 수행하는 방법을 모르겠습니다. 원본을 다시 편집해야합니다. 질문?
Dinah

50

말 그대로 질문에 대답하십시오. 항상 그런 것은 아닙니다x == x

double zero = 0.0;
double a[] = { 0,0,0,0,0, zero/zero}; // NaN
cout << (a[5] == 5[a] ? "true" : "false") << endl;

인쇄물

false

27
실제로 "nan"은 자체와 같지 않습니다 : cout << (a[5] == a[5] ? "true" : "false") << endl;is false입니다.
TrueY

8
@TrueY : 그는 NaN 사례에 대해 구체적으로 언급했습니다 (특히 x == x항상 그런 것은 아닙니다). 나는 이것이 그의 의도라고 생각합니다. 그래서 그는 기술적으로 정확합니다 (그리고 아마도 그들이 말한대로 가장 좋은 종류입니다!).
Tim Čas 1

3
문제는 C에 관한 것이며 코드는 C 코드가 아닙니다. 거기에 또한 NAN에서 <math.h>더 나은보다, 0.0/0.0때문에, 0.0/0.0때 UB입니다 __STDC_IEC_559__정의되지 않은 (대부분의 구현은 정의하지 않습니다 __STDC_IEC_559__,하지만 대부분의 구현에 대한 0.0/0.0의지 여전히 일)
12,431,234,123,412,341,234,123

26

이 추악한 구문이 "유용"하거나 같은 배열의 위치를 ​​참조하는 인덱스 배열을 다루고 싶을 때 아주 재미있을 수 있습니다. 중첩 된 대괄호를 대체하고 코드를 더 읽기 쉽게 만들 수 있습니다!

int a[] = { 2 , 3 , 3 , 2 , 4 };
int s = sizeof a / sizeof *a;  //  s == 5

for(int i = 0 ; i < s ; ++i) {  

           cout << a[a[a[i]]] << endl;
           // ... is equivalent to ... 
           cout << i[a][a][a] << endl;  // but I prefer this one, it's easier to increase the level of indirection (without loop)

}

물론 실제 코드에는 유스 케이스가 없다고 확신하지만 어쨌든 흥미 롭습니다. :)


당신이 볼 때 i[a][a][a]i는 배열에 대한 포인터 또는 배열 또는 배열에 대한 포인터의 배열이라고 생각합니다 ... 그리고 a색인입니다. 가 a[a[a[i]]]보이면 a는 배열 또는 배열에 대한 포인터 i이며 색인 이라고 생각합니다 .
12,431,234,123,412,341,234,123

1
와! 이 "멍청한"기능을 아주 잘 사용합니다. 일부 문제에서 알고리즘 콘테스트에 유용 할 수 있음))
Breusov Serge

26

좋은 질문 / 답변.

C 포인터와 배열이 동일 하지 않다는 점을 지적하고 싶지만 ,이 경우 차이점은 필수는 아닙니다.

다음 선언을 고려하십시오.

int a[10];
int* p = a;

이어 a.out, 심볼은 a배열의 선두의 어드레스로하고, 심볼 p포인터가 저장되는 주소이고, 그 메모리 위치에 대한 포인터의 값은 상기 어레이의 시작이다.


2
아니요, 기술적으로는 다릅니다. 일부 b를 int * const로 정의하고 배열을 가리키는 경우 여전히 포인터입니다. 즉, 기호 테이블에서 b는 주소를 저장하는 메모리 위치를 나타내며, 이는 배열이있는 위치를 나타냅니다. .
PolyThinker

4
아주 좋은 지적입니다. 하나의 모듈에서 전역 기호를 char s [100]으로 정의하고 extern char * s로 선언 할 때 매우 불쾌한 버그가 발생한 것을 기억합니다. 다른 모듈에서. 그것을 모두 연결 한 후 프로그램은 매우 이상하게 행동했습니다. extern 선언을 사용하는 모듈이 배열의 초기 바이트를 char에 대한 포인터로 사용했기 때문입니다.
조르지오

1
원래 C의 조부모 BCPL에서 배열은 포인터였습니다. 즉, 당신이 썼을 때 얻은 것 (나는 C로 음역했습니다) int a[10]은 'a'라는 포인터였습니다. 따라서 a + i와 j + i는 같은 형식을 갖습니다. 몇 개의 메모리 위치의 내용을 추가하십시오. 실제로 BCPL은 유형이 없으므로 동일하다고 생각합니다. BCPL은 순전히 단어 중심이기 때문에 (워드 어드레싱 된 머신에서도), size-type 스케일링은 적용되지 않았습니다.
dave

차이점을 이해하는 가장 좋은 방법은 비교 int*p = a;하는 것입니다 int b = 5; . 후자에서 "b"와 "5"는 모두 정수이지만 "b"는 변수이고 "5"는 고정 된 값입니다. 마찬가지로 "p"& "a"는 문자의 주소이지만 "a"는 고정 된 값입니다.
James Curran

20

C의 포인터에 대해서는

a[5] == *(a + 5)

그리고 또한

5[a] == *(5 + a)

따라서 사실이다 a[5] == 5[a].


15

답이 아니라 생각을위한 음식입니다. 클래스에 오버로드 된 인덱스 / 첨자 연산자가 있으면 표현식 0[x]이 작동하지 않습니다.

class Sub
{
public:
    int operator [](size_t nIndex)
    {
        return 0;
    }   
};

int main()
{
    Sub s;
    s[0];
    0[s]; // ERROR 
}

우리는 int 클래스에 액세스 할 수 없으므로이 작업을 수행 할 수 없습니다 :

class int
{
   int operator[](const Sub&);
};

2
class Sub { public: int operator[](size_t nIndex) const { return 0; } friend int operator[](size_t nIndex, const Sub& This) { return 0; } };
벤 Voigt

1
실제로 컴파일을 시도 했습니까? 클래스 외부에서 구현할 수없는 연산자 세트가 있습니다 (예 : 비 정적 함수)!
Ajay

3
죄송합니다. " operator[]정확히 하나의 매개 변수를 가진 비 정적 멤버 함수 여야합니다." 에 대한 제한 사항에 대해 잘 알고 operator=있었지만 적용되지 않았다고 생각했습니다 [].
벤 Voigt

1
당신의 정의를 변경하는 경우 물론, []운영자,이 경우 ... 다시 동등하지 않을 것 a[b]IS가 동일 *(a + b)하고이 변경, 당신은 또한 오버로드해야합니다 int::operator[](const Sub&);int클래스 ... 아니다
루이스 콜로라도

7
이것은 ... C가 아닙니다.
MD XF

11

C의 포인터와 배열에 대한 설명이 아주 훌륭합니다. 테드 젠슨 (Ted Jensen)의 있습니다.

테드 젠슨은 다음과 같이 설명했다.

실제로 이것은 사실입니다. a[i]*(a + i) 문제없이 대체 할 수 있습니다 . 실제로 컴파일러는 두 경우 모두 동일한 코드를 만듭니다. 따라서 포인터 산술은 배열 인덱싱과 동일합니다. 어느 구문이든 동일한 결과를 생성합니다.

이것은 포인터와 배열이 같은 것이 아니라고 말하는 것이 아닙니다. 우리는 배열의 주어진 요소를 식별하기 위해 두 가지 구문 중 하나를 선택할 수 있는데, 하나는 배열 인덱싱을 사용하고 다른 하나는 포인터 산술을 사용하여 동일한 결과를 산출합니다.

이제이 마지막 표현식을 살펴보면, 그 일부가 .. (a + i)+ 연산자와 C 표현식의 규칙을 사용하여 이러한 표현식이 정식이라는 간단한 추가입니다. 즉 (a + i)는와 같습니다 (i + a). 따라서 우리는 *(i + a)처럼 쉽게 쓸 수 있습니다 *(a + i). 그러나 *(i + a)올 수 있었다 i[a]! 이 모든 것에서 다음과 같은 호기심이 생깁니다.

char a[20];

쓰기

a[3] = 'x';

글쓰기와 동일

3[a] = 'x';

4
a + i는 포인터 산술이기 때문에 간단한 추가가 아닙니다. a 요소의 크기가 1 (char)이면 yes, 정수 +와 같습니다. 그러나 (예를 들어) 정수이면 + 4 * i와 같습니다.
Alex Brown

@AlexBrown 예, 포인터 산술입니다. 따라서 'a'를 (char *)로 캐스팅하지 않는 한 (int가 4 자라고 가정하지 않는 한) 마지막 문장이 잘못되었습니다. 왜 그렇게 많은 사람들이 포인터 산술의 실제 가치 결과에 매달리고 있는지 이해하지 못합니다. 포인터 산술의 전체 목적은 기본 포인터 값을 추상화하고 프로그래머가 주소 값이 아닌 조작되는 객체에 대해 생각하게하는 것입니다.
jschultz410

8

나는 그 질문에 대한 답을 알고 있지만이 설명을 공유하는 것을 거부 할 수 없었다.

컴파일러 디자인의 원리를 기억 a합니다. int배열이고 크기 int가 2 바이트이고 기본 주소 a가 1000이라고 가정합니다.

어떻게 a[5]작동합니다 ->

Base Address of your Array a + (5*size of(data type for array a))
i.e. 1000 + (5*2) = 1010

그래서,

마찬가지로 C 코드가 3 주소 코드로 분류 5[a]되면->

Base Address of your Array a + (size of(data type for array a)*5)
i.e. 1000 + (2*5) = 1010 

따라서 기본적으로 두 명령문 모두 메모리에서 동일한 위치를 가리 키므로 a[5] = 5[a].

이 설명은 배열의 음수 인덱스가 C에서 작동하는 이유이기도합니다.

즉 내가 접근 a[-5]하면 나에게 줄 것이다

Base Address of your Array a + (-5 * size of(data type for array a))
i.e. 1000 + (-5*2) = 990

위치 990의 객체를 반환합니다.


6

에서 C 배열 , arr[3]그리고 3[arr]동일하며, 자신의 해당 포인터 표기법은 *(arr + 3)*(3 + arr). 그러나 반대로 [arr]3거나하는 [3]arr등, 올바르지 않은 및 구문 오류로 발생합니다 (arr + 3)*(3 + arr)*올바른 표현이 아니다. 이유는 역 참조 연산자가 주소 이후가 아니라 표현식에서 생성 된 주소 앞에 배치되어야하기 때문입니다.


6

C 컴파일러에서

a[i]
i[a]
*(a+i)

배열의 요소를 참조하는 다른 방법입니다! (전세계가 아님)


5

이제 조금 역사가 있습니다. 다른 언어 중에서도 BCPL은 C의 초기 개발에 상당히 큰 영향을 미쳤습니다. BCPL에서 다음과 같이 배열을 선언 한 경우 :

let V = vec 10

실제로는 10 단어가 아닌 11 단어의 메모리를 할당했습니다. 따라서 C와 달리 이름 지정 V는 해당 위치로 이동하여 배열의 0 번째 요소의 주소를 선택했습니다. 따라서 BCPL의 배열 간접 참조

let J = V!5

J = !(V + 5)배열의 기본 주소를 얻기 위해 V를 페치해야 했으므로 실제로 BCPL 구문을 사용하여 수행해야 했습니다. 따라서 V!55!V 동의어였다. 일화적인 관찰로서 WAFL (Warwick Functional Language)은 BCPL로 작성되었으며, 내 기억을 최대한 활용하기 위해 데이터 스토리지로 사용되는 노드에 액세스하기 위해 전자보다 후자의 구문을 사용하는 경향이있었습니다. 이것은 35 년에서 40 년 전의 것이 었으므로 나의 기억은 조금 녹슬 었습니다. :)

여분의 스토리지 단어를 사용하지 않고 컴파일러가 어레이의 기본 주소를 나중에 명명 할 때 삽입하도록하는 혁신. C 역사 보고서에 따르면 이것은 구조가 C에 추가 된 시점에서 발생했습니다.

하는 것으로 !BCPL에 단항 접두사 연산자와 간접을하고 두 경우 모두 진 중위 연산자, 둘이었다. 이진 형식에는 간접 처리를 수행하기 전에 두 피연산자가 추가 된 것이 포함되어 있습니다. BCPL (및 B)의 단어 지향적 특성을 고려할 때 이것은 실제로 많은 의미가 있습니다. 「포인터와 정수」의 제한은, 데이터 형을 취득했을 때에 C에 필요 sizeof하게되어 일이되었습니다.


1

글쎄, 이것은 언어 지원 때문에 가능한 기능입니다.

컴파일러는 a[i]로 해석 *(a+i)하고 표현식은로 5[a]평가됩니다 *(5+a). 덧셈은 교환 적이므로 둘 다 동일하다는 것이 밝혀졌습니다. 따라서 표현은true .


중복되지만 이것은 명확하고 간결하며 짧습니다.
Bill K

0

C에서

 int a[]={10,20,30,40,50};
 int *p=a;
 printf("%d\n",*p++);//output will be 10
 printf("%d\n",*a++);//will give an error

포인터는 "가변"

배열 이름은 "니모닉"또는 "동의어"

p++; 유효하지만 a++ 유효하지 않습니다

a[2] 이 둘의 내부 연산이 다음과 같기 때문에 2 [a]와 같습니다.

내부적으로 계산 된 "포인터 산술"

*(a+3) 같다 *(3+a)


-4

포인터 유형

1) 데이터에 대한 포인터

int *ptr;

2) 데이터에 대한 const 포인터

int const *ptr;

3) const 데이터에 대한 const 포인터

int const *const ptr;

배열은 목록에서 (2) 유형입니다 . 한 번에 하나의 주소가 해당 포인터에서 초기화
될 때 배열정의하면 프로그램에서 const 값을 변경하거나 수정할 수 없다는 것을 알기 때문에 컴파일시 오류 가 발생합니다 시각

큰 차이 내가 찾은입니다 ...

주소로 포인터를 다시 초기화 할 수 있지만 배열과 동일한 경우는 아닙니다.

======
그리고 당신의 질문으로 돌아가는
a[5]것은 아무것도 아니지만 *(a + 5)
당신은
a -리스트에 (2) 유형의 포인터처럼 주소 (사람들이 기본 주소라고 부릅니다)를 포함 함으로써 쉽게 이해할 수 있습니다
[] -그 연산자는 pointer로 교체 가능 *.

마지막으로 ...

a[5] == *(a +5) == *(5 + a) == 5[a] 
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.