source

어레이 구문과 포인터 구문 및 코드 생성 비교

nicesource 2022. 11. 5. 17:24
반응형

어레이 구문과 포인터 구문 및 코드 생성 비교

리처드 리스의 "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).

  1. A postfix expression followed by an expression in square brackets postfix 식 뒤에 대괄호로 둘러싸인 식[]는 배열 오브젝트 요소의 첨자 표기입니다.첨자 연산자의 정의는 동일합니다. 바이너리에 적용되는 변환 규칙 때문입니다.+산 연?E1어레이 개체(예: 어레이 개체) 및 μ μ μ μ μ μ μ μ μ μ μ μ μ μ μ μ μ μ μ E2정수입니다.E1[E2] designates the 를 지정합니다.E2E1(일부러)

또한 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이 정확하게 동작한다는 것입니다.예:

C-Code

Assembly code

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

반응형