어레이 구문과 포인터 구문 및 코드 생성 비교
리처드 리스의 "C 포인터 이해와 사용"이라는 책에 85페이지에 나와 있습니다.
int vector[5] = {1, 2, 3, 4, 5};
「 」가 .
vector[i]
입니다.*(vector+i)
.vector[i]
는 로케이션 벡터에서 시작하는 머신 코드를 생성합니다.i
이 위치에서 위치를 지정하고 해당 내용을 사용합니다. 「」*(vector+i)
는 로케이션에서 합니다.vector
, 가 추가됩니다.i
해당 주소로 전송한 후 해당 주소의 내용을 사용합니다.결과는 동일하지만 생성된 기계 코드가 다릅니다.대부분의 프로그래머에게 이 차이는 거의 중요하지 않습니다.
여기서 발췌를 보실 수 있습니다.이 글의 의미는 무엇입니까?어떤 맥락에서 컴파일러가 이 두 가지에 대해 다른 코드를 생성합니까?베이스에서 베이스로 이동하는 것과 베이스로 추가하는 것의 차이가 있습니까?다른 기계 코드를 생성하는 GCC에서 이 기능을 사용할 수 없었습니다.
그 인용문은 단지 틀렸다.이 쓰레기는 올해 10년 동안 출판되어 있다.이런 쓰레기가 10년 후에도 여전히 출판된다는 것은 매우 비극적이다. In fact, the C Standard defines 실제로 C 표준은 다음을 정의한다.x[y]
as ~하듯이*(x+y)
....
그 페이지의 뒷부분에 있는 가치관에 대한 부분도 완전히 틀렸다.
IMHO, 이 책을 사용하는 가장 좋은 방법은 재활용 통에 넣거나 태우는 것입니다.
I've got 2 C files: 2개의 C 파일이 있습니다.ex1.c
% cat ex1.c
#include <stdio.h>
int main (void) {
int vector[5] = { 1, 2, 3, 4, 5 };
printf("%d\n", vector[3]);
}
and 그리고.ex2.c
,,,,
% cat ex2.c
#include <stdio.h>
int main (void) {
int vector[5] = { 1, 2, 3, 4, 5 };
printf("%d\n", *(vector + 3));
}
둘 다 어셈블리로 컴파일하고 생성된 어셈블리 코드의 차이를 보여 줍니다.
% gcc -S ex1.c; gcc -S ex2.c; diff -u ex1.s ex2.s
--- ex1.s 2018-07-17 08:19:25.425826813 +0300
+++ ex2.s 2018-07-17 08:19:25.441826756 +0300
@@ -1,4 +1,4 @@
- .file "ex1.c"
+ .file "ex2.c"
.text
.section .rodata
.LC0:
Q.E.D.
C 표준은 매우 명시적으로 명시되어 있다(C11 n1570 6.5.2.1p2).
- A postfix expression followed by an expression in square brackets postfix 식 뒤에 대괄호로 둘러싸인 식
[]
는 배열 오브젝트 요소의 첨자 표기입니다.첨자 연산자의 정의는 와 동일합니다. 바이너리에 적용되는 변환 규칙 때문입니다.+
산 연?E1
어레이 개체(예: 어레이 개체) 및 μ μ μ μ μ μ μ μ μ μ μ μ μ μ μ μ μ μ μE2
정수입니다.E1[E2]
designates the 를 지정합니다.E2
의E1
(일부러)
또한 as-if 규칙은 여기에 적용됩니다.프로그램의 동작이 동일하면 컴파일러는 시멘틱스가 동일하지 않아도 같은 코드를 생성할 수 있습니다.
인용된 구절은 상당히 틀렸다.은 「 」vector[i]
★★★★★★★★★★★★★★★★★」*(vector+i)
완전히 동일하며 모든 상황에서 동일한 코드를 생성할 수 있습니다.
은 「 」vector[i]
★★★★★★★★★★★★★★★★★」*(vector+i)
의 정의는 동일합니다.이것은 C 프로그래밍 언어의 핵심이자 기본 속성입니다.유능한 C 프로그래머라면 누구나 이것을 이해한다.'C 포인터의 이해와 사용'이라는 제목의 책을 쓴 사람은 누구나 이것을 이해해야 한다.이 점은 C 컴파일러의 저자라면 누구나 이해할 수 있습니다.2개의 fragment는 우연히 같은 코드를 생성하는 것이 아니라 사실상 모든 C 컴파일러가 거의 즉시 다른 형식으로 변환되기 때문에 코드 생성 단계에 도달할 때까지 어떤 형식이 처음에 사용되었는지조차 알 수 없습니다.(C 컴파일러가 si를 생성한 적이 있다면 매우 놀랄 것입니다.)에 대해 영특하게 다른 코드vector[i]
*(vector+i)
그리고 실제로 인용된 텍스트는 그 자체와 모순됩니다.말씀하신 것처럼 두 구절은
「」
vector[i]
는 로케이션에서 합니다.vector
, 이동i
이 위치에서 위치를 지정하고 해당 내용을 사용합니다.
그리고.
표기법
*(vector+i)
로케이션에서 시작하는 머신 코드를 생성합니다.vector
, 가 추가됩니다.i
해당 주소로 전송한 후 해당 주소의 내용을 사용합니다.
기본적으로 같은 말을 합니다.
그의 언어는 이전 C FAQ 목록의 질문 6.2와 매우 유사합니다.
...컴파일러가 다음 식을 참조할 때
a[3]
로케이션 「」에서 개시하기 위한 코드를 송신합니다.a
"세 번 지나쳐서 캐릭터를 데려와라.그 표정을 보면p[3]
로케이션 「」에서 개시하기 위한 코드를 송신합니다.p
여기서 포인터 값을 가져오고 포인터에 3을 더하고 마지막으로 가리키는 문자를 가져옵니다.
하지만 여기서 중요한 차이점은 어레이와 포인터라는 것입니다.FAQ 목록에는 다음 정보가 없습니다.a[3]
대*(a+3)
에 대해서입니다.a[3]
(또는*(a+3)
)여기서a
어레이와p[3]
(또는*(p+3)
)여기서p
는 포인터입니다.(물론 이 두 케이스는 다른 코드를 생성합니다.배열과 포인터는 다르기 때문입니다.FAQ 리스트에서 설명하듯이 포인터 변수에서 주소를 가져오는 것은 어레이의 주소를 사용하는 것과는 근본적으로 다릅니다.
이 기준서는 다음과 같은 동작을 규정한다.arr[i]
언제arr
분해와 동등한 배열 객체입니다.arr
포인터에 추가i
결과를 참조합니다.모든 표준 정의 사례에서 행동은 동일하지만, 일부 컴파일러는 표준이 요구하는 조치와 다음과 같은 조치를 유용하게 처리하는 경우가 있다.arrayLvalue[i]
그리고.*(arrayLvalue+i)
따라서 다를 수 있습니다.
예를 들어,
char arr[5][5];
union { unsigned short h[4]; unsigned int w[2]; } u;
int atest1(int i, int j)
{
if (arr[1][i])
arr[0][j]++;
return arr[1][i];
}
int atest2(int i, int j)
{
if (*(arr[1]+i))
*((arr[0])+j)+=1;
return *(arr[1]+i);
}
int utest1(int i, int j)
{
if (u.h[i])
u.w[j]=1;
return u.h[i];
}
int utest2(int i, int j)
{
if (*(u.h+i))
*(u.w+j)=1;
return *(u.h+i);
}
GCC의 test1용 생성된 코드는 arr[1][i] 및 ar[0][j]에일리어스를지정할수없다고전제합니다만, test2용 생성된 코드에서는 포인터 산술이 어레이 전체에 액세스 할 수 있도록 합니다.반면에서는 gcc는 utest1, lvalue 식 u.h[i] 및 u.w[j]는 둘 다 같은 유니언에 액세스하지만 utest2의 *(u.h+i)와 *(u.w+j)에 대해 동일한 것을 알 수 있을 만큼 복잡하지 않습니다.
원본 텍스트가 의미하는 것은 일부 컴파일러가 실행하거나 실행하지 않을 수 있는 최적화라고 생각합니다.
예:
for ( int i = 0; i < 5; i++ ) {
vector[i] = something;
}
대.
for ( int i = 0; i < 5; i++ ) {
*(vector+i) = something;
}
첫 번째 경우, 최적화 컴파일러는 어레이를 검출할 수 있습니다.vector
요소별로 반복되며, 따라서 다음과 같은 것을 생성합니다.
void* tempPtr = vector;
for ( int i = 0; i < 5; i++ ) {
*((int*)tempPtr) = something;
tempPtr += sizeof(int); // _move_ the pointer; simple addition of a constant.
}
가능한 경우 대상 CPU의 포인터 증가 후 지침을 사용할 수도 있습니다.
두 번째 경우, 컴파일러는 어떤 "임의" 포인터 산술식을 통해 계산된 주소가 각 반복에서 고정된 양을 단조롭게 전진시키는 동일한 속성을 보여주는 것을 보는 것이 더 어렵다.따라서 최적화를 찾아내지 못할 수 있습니다.((void*)vector+i*sizeof(int))
각 반복에서 추가 곱셈을 사용합니다.이 경우 "이동"되는 (임시) 포인터는 없고 임시 주소만 다시 계산됩니다.
단, 이 스테이트먼트가 모든 버전의 C 컴파일러에 공통적으로 적용되는 것은 아닙니다.
업데이트:
위의 예를 확인했습니다.최적화를 활성화하지 않으면 적어도 gcc-8.1 x86-64는 첫 번째(어레이 인덱스)보다 두 번째(포인트 산술) 형식에 대해 더 많은 코드(2개의 추가 명령)를 생성하는 것으로 보입니다.
참조: https://godbolt.org/g/7DaPHG
다만, 최적화가 유효하게 되어 있는 경우)-O
...-O3
생성된 코드는 양쪽에서 동일(길이)입니다.
이 "좁음"에 대해 답변해 보겠습니다(다른 사람들은 "있는 그대로"라는 설명이 다소 부족/불완전/오해되는 이유를 이미 설명했습니다).
어떤 맥락에서 컴파일러가 이 두 가지에 대해 다른 코드를 생성합니까?
"매우 최적화되지 않은" 컴파일러는 구문 분석 중에 다음과 같은 차이가 있기 때문에 거의 모든 컨텍스트에서 다른 코드를 생성할 수 있습니다.x[y]
이며, is is ( ( is is is 、 is is 、 is is ( into),),입입입 is ) 。*(x+y)
는 2개의 식입니다(포인터에 정수를 추가한 후 참조 해제).물론 (파싱 중에도) 이것을 인식하고 동일하게 취급하는 것은 어렵지 않습니다만, 심플하고 빠른 컴파일러를 쓰는 경우는, 「너무 많은 스마트를 들이지 않는다」라고 하는 것은 피할 수 있습니다.예를 들어 다음과 같습니다.
char vector[] = ...;
char f(int i) {
return vector[i];
}
char g(int i) {
return *(vector + i);
}
컴파일러가 중, "" " " " "f()
「해, ( CPU의 「」, 「」, 「」, 「CPU」등의 것을 생성할 수 .
MOVE D0, [A0 + D1] ; A0/vector, D1/i, D0/result of function
의 경우 »g()
컴파일러는 두 가지를 확인합니다.처음에는 디레퍼런스('아직 오지 않은 것')와 다음으로 포인터/어레이에 정수를 추가하는 것입니다.따라서 최적화가 되지 않으면 다음과 같이 끝납니다.
MOVE A1, A0 ; A1/t = A0/vector
ADD A1, D1 ; t += i/D1
MOVE D0, [A1] ; D0/result = *t
에는 복잡한 하는 것을 사람도 .컴파일러에 따라서는 복잡한 명령어를 사용하는 것을 싫어할 수도 있습니다.f()
), 명령어가 수 .
베이스에서 베이스로 이동하는 것과 베이스로 추가하는 것의 차이가 있습니까?
그 책에 있는 묘사는 거의 틀림없이 잘 표현되지 않았다.그런데 위에서와 같은 차이점을 설명하고 싶었던 것 같아요.색인화(기본값에서 이동)는 하나의 표현이고, '추가 후 참조 해제'는 두 표현입니다.
이는 언어 정의가 아닌 컴파일러 구현에 관한 것으로, 이 책에도 명시되어 있어야 합니다.
컴파일러의 종류에 따라 코드를 테스트했습니다만, 그 중 대부분은 양쪽 명령에서 같은 어셈블리 코드를 얻을 수 있었습니다(최적화 기능이 없는 x86 용으로 테스트).흥미로운 점은 gcc 4.4.7이 정확하게 동작한다는 것입니다.예:
ARM이나 MIPS와 같은 다른 언어도 같은 동작을 하고 있지만 저는 모든 것을 테스트하지 않았습니다.다른 점이 있는 것 같습니다만, 그 이후의 버전의 gcc에서는 이 버그를 「수정」했습니다.
이것은 C에서 사용되는 어레이 구문 예시입니다.
int a[10] = {1,2,3,4,5,6,7,8,9,10};
언급URL : https://stackoverflow.com/questions/51373516/array-syntax-vs-pointer-syntax-and-code-generation
'source' 카테고리의 다른 글
타임스탬프 열 업데이트 시 이상한 동작 (0) | 2022.11.05 |
---|---|
Vue 구성 요소가 레이블로 표시되지 않음 (0) | 2022.11.05 |
Java Array List - 두 목록이 동일한지, 순서는 중요하지 않은지 어떻게 알 수 있습니까? (0) | 2022.11.05 |
href 식의 역할은 무엇입니까? (0) | 2022.11.05 |
변수 $this는 PHP에서 무엇을 의미합니까? (0) | 2022.11.05 |