source

스택을 정렬한다는 것은 무엇을 의미합니까?

gigabyte 2022. 7. 17. 16:47
반응형

스택을 정렬한다는 것은 무엇을 의미합니까?

저는 고급 코더였고 아키텍처는 매우 생소하기 때문에 조립에 관한 튜토리얼을 읽기로 했습니다.

http://en.wikibooks.org/wiki/X86_Assembly/Print_Version

튜토리얼 맨 아래 Hello World! 프로그램을 변환하는 방법에 대한 설명

#include <stdio.h>

int main(void) {
    printf("Hello, world!\n");
    return 0;
}

동일한 어셈블리 코드로 변환되어 다음 항목이 생성되었습니다.

        .text
LC0:
        .ascii "Hello, world!\12\0"
.globl _main
_main:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $8, %esp
        andl    $-16, %esp
        movl    $0, %eax
        movl    %eax, -4(%ebp)
        movl    -4(%ebp), %eax
        call    __alloca
        call    ___main
        movl    $LC0, (%esp)
        call    _printf
        movl    $0, %eax
        leave
        ret

한 줄에 대해서

andl    $-16, %esp

설명은 다음과 같습니다.

이 코드는 ESP에 0xFFFF0으로 "및" 스택을 다음으로 낮은 16바이트 경계에 맞춥니다.Mingw 소스 코드를 조사하면 정렬된 주소에서만 작동하는 "_main" 루틴에 나타나는 SIMD 명령일 수 있습니다.우리의 루틴에는 SIMD 명령이 포함되어 있지 않기 때문에 이 행은 불필요합니다.

나는 이 점을 이해하지 못한다.스택을 다음 16바이트 경계에 맞추는 것이 어떤 의미인지, 왜 그것이 필요한지 설명해 주실 수 있나요? 그그어 is는 요?andl성해야? ??

._main 않습니다 (스택 포인터의 주소는 예에 불과합니다).

|    existing     |
|  stack content  |
+-----------------+  <--- 0xbfff1230

%ebp, 8을 빼다.%esp「 」 「 」 、 「 」

|    existing     |
|  stack content  |
+-----------------+  <--- 0xbfff1230
|      %ebp       |
+-----------------+  <--- 0xbfff122c
:    reserved     :
:     space       :
+-----------------+  <--- 0xbfff1224

이제 ㅇㅇ, ㅇㅇ.andl를 0으로 합니다.%esp 예에서는 4바이트를 추가로 예약하는 효과가 있습니다.

|    existing     |
|  stack content  |
+-----------------+  <--- 0xbfff1230
|      %ebp       |
+-----------------+  <--- 0xbfff122c
:    reserved     :
:     space       :
+ - - - - - - - - +  <--- 0xbfff1224
:   extra space   :
+-----------------+  <--- 0xbfff1220

요점은 메모리 내의 여러 워드에 대해 병렬 연산을 수행할 수 있는 "SIMD(Single Instruction, Multiple Data)" 명령(x86-land에서는 "Streaming SIMD Extensions"의 "SSE"라고도 함)이 있지만, 이러한 여러 워드가 16바이트의 주소에서 시작하는 블록이어야 한다는 것입니다.

의 특정 오프셋을 수 .%esp는 「」가 되어 있기 때문에).%esp(을)그러나 이 방법으로 스택포인터를 의도적으로 정렬함으로써 컴파일러는 스택포인터에 16바이트의 배수가 추가되면 16바이트로 정렬된 주소가 된다는 것을 알고 있습니다.SIMD를 사용합니다.

이것은 스택 고유의 것이 아니라 일반적으로 정렬된 것처럼 들립니다.정수배수라는 용어를 생각해 보세요.

메모리에 1바이트 단위인 항목이 있는 경우 모두 정렬되어 있다고 가정합니다.크기가 2바이트이고 정수 곱하기 2가 0, 2, 4, 6, 8 등 정렬됩니다.그리고 정수 배수가 아닌 1, 3, 5, 7은 정렬되지 않습니다.크기가 4바이트, 정수배수 0, 4, 8, 12 등이 정렬되어 있고, 1, 2, 3, 5, 6, 7 등이 정렬되어 있지 않습니다.8, 0, 8, 16, 24 및 16 16, 32, 48, 64 등에 대해서도 마찬가지입니다.

즉, 항목의 기본 주소를 보고 정렬되어 있는지 확인할 수 있습니다.

크기(바이트 단위), 주소 형식1, xxxxxxxxxxxxxxxxxxx2, xxxxxxx04, xxxxx008, xxxx00016,xx000032,xx00,00064,x000000등등

컴파일러가 .text 세그먼트의 명령과 데이터를 혼합하는 경우 필요에 따라 데이터를 정렬하는 것은 매우 간단합니다(아키텍처에 따라 다름).그러나 스택은 실행 시 컴파일러는 일반적으로 실행 시 스택의 위치를 결정할 수 없습니다.따라서 로컬 변수를 정렬해야 하는 경우 실행 시 코드가 스택을 프로그래밍 방식으로 조정해야 합니다.

예를 들어 스택에 2개의 8바이트 항목, 총 16바이트가 있으며, 이 항목들을 정렬(8바이트 경계)할 필요가 있다고 가정합니다.입력 시 이 함수는 스택 포인터에서 평소처럼 16을 빼서 이 두 항목을 위한 공간을 확보합니다.하지만 이들을 정렬시키려면 더 많은 코드가 필요합니다.이 2개의 8바이트 항목을 8바이트 경계에 정렬하고 16을 뺀 후의 스택포인터가 0xFF82일 경우 하위 3비트는 0이 아니므로 정렬되지 않습니다.하위 3비트는 0b010입니다.일반적인 의미에서 0xFF82에서 2를 빼면 0xFF80이 됩니다.2라고 판별하려면 0b111(0x7)을 더하고 그 양을 빼야 합니다.즉, a와 뺄셈을 합니다.단, 0x7(~0x7 = 0xFFF...)의 보수가 있으면 지름길로 갈 수 있습니다.FFF8) 1개의 alu 연산을 사용하면 0xFF80을 얻을 수 있습니다(컴파일러와 프로세서가 1개의 opcode 방식으로 실행할 수 있는 경우, 를 제외한 값보다 비용이 많이 들 수 있습니다).

이것은, 사용의 프로그램이 하고 있던 동작인 것 같습니다.-16을 사용한 앤딩은 0xFFFF를 사용한 앤딩과 동일합니다.FFF0. 결과적으로 주소가 16바이트 경계에 정렬됩니다.

마지막으로, 일반적인 스택 포인터와 같은 메모리 다운이 높은 주소에서 낮은 주소까지 기능하는 경우,

 
sp = sp & (~(n-1))

여기서 n은 정렬할 바이트 수입니다(전원이어야 하지만 대부분의 정렬은 보통 2의 거듭제곱을 포함합니다).malloc(주소가 낮음에서 높음으로 증가)를 실행했다고 하고, 어떤 주소의 위치를 맞추고 싶은 경우(적어도 위치 맞추기 크기만큼 필요한 것보다 많은 malloc을 실행하도록 기억하십시오.

if(ptr&(~(n-)) { ptr = (ptr+n)&(~(n-1))}

또는 if를 꺼내서 매번 add와 mask를 수행합니다.

대부분의 비 x86 아키텍처에는 얼라인먼트 규칙과 요건이 있습니다.명령어 세트에서는 x86이 지나치게 유연하지만 실행 시 x86의 비정렬 액세스에 대한 패널티를 지불할 수 있습니다.따라서 할 수 있지만 다른 아키텍처와 마찬가지로 정렬 상태를 유지하도록 노력해야 합니다.아마도 그것이 이 코드가 하고 있었던 것일 것이다.

프로세서가 메모리에서 레지스터로 데이터를 로드하는 경우 기본 주소와 크기로 액세스해야 합니다.예를 들어 주소 10100100에서4 바이트를 가져옵니다.이 예의 마지막에는 2개의 제로가 있는 것에 주의해 주세요.이는 4바이트가 저장되기 때문에 101001의 선두 비트가 중요하기 때문입니다.(프로세서는 실제로 101001XX를 취득함으로써 "상관없음"을 통해 이들 비트에 액세스합니다.)

따라서 메모리 내의 어떤 것을 정렬하는 것은 원하는 항목의 주소가 충분한 0바이트를 가지도록 데이터를 정렬하는 것을 의미합니다.위의 예에서는 마지막 2비트가 0이 아니기 때문에 10100101에서4바이트를 취득할 수 없습니다.그 때문에 버스 에러가 발생합니다.따라서 주소를 10101000까지 확장해야 합니다(그리고 그 과정에서 3개의 주소 위치를 낭비합니다).

컴파일러는 이 작업을 자동으로 수행하며 어셈블리 코드에 표시됩니다.

이는 C/C++에서의 최적화로 나타납니다.

struct first {
    char letter1;
    int number;
    char letter2;
};

struct second {
    int number;
    char letter1;
    char letter2;
};

int main ()
{
    cout << "Size of first: " << sizeof(first) << endl;
    cout << "Size of second: " << sizeof(second) << endl;
    return 0;
}

출력은

Size of first: 12
Size of second: 8

둘의 재배치char님의 뜻은int컴파일러가 패딩을 통해 기본 주소를 범핑할 필요가 없습니다.그래서 두 번째 사이즈가 더 작아요.

이것은 바이트 정렬과 관련이 있습니다.특정 아키텍처에서는 특정 조작 세트에 사용되는 주소를 특정 비트 경계에 맞춰야 합니다.

즉, 예를 들어 포인터에 64비트 정렬을 원하는 경우 주소 지정 가능한 메모리 전체를 개념적으로 64비트 청크로 분할할 수 있습니다.주소가 이러한 청크 중 하나에 정확히 맞으면 "정렬"되고, 청크의 일부와 다른 청크의 일부이면 정렬되지 않습니다.

바이트 얼라인먼트의 중요한 기능(숫자가 2의 거듭제곱이라고 가정)은 주소의 최하위 X비트가 항상 0이라는 것입니다.이것에 의해, 프로세서는, 하단의 X비트를 사용하지 않는 것만으로, 보다 적은 비트로 보다 많은 주소를 나타낼 수 있습니다.

홀수 주소가 아닌 짝수 주소에만 있어야 합니다.접근하는 퍼포먼스가 부족하기 때문입니다.

이 '그림'을 상상해 보세요.

어드레스xxx0123456789def01234567...
[------][------][------] ...
레지스터

에서의 값은 8개의 "슬라이드"를 여러 개(64비트) 레지스터에 쉽게 삽입할 수 있습니다.

어드레스56789파운드...
[------][------][------] ...
레지스터

물론 8바이트 단위로 "걷기"를 등록합니다.

주소 xxx5의 값을 레지스터에 넣는 것은 매우 어렵습니다:-)


편집 및 -16

-16은 바이너리로 11111111111111111111110000입니다.

-16을 사용하여 "and"를 지정하면 마지막 4비트가 0으로 설정된 값을 얻을 수 있습니다.또는 16의 배수.

언급URL : https://stackoverflow.com/questions/4175281/what-does-it-mean-to-align-the-stack

반응형