대부분의 최신 시스템에서 스택 성장 방향은 무엇입니까?


답변:


147

스택 증가는 일반적으로 운영 체제 자체가 아니라 실행중인 프로세서에 따라 다릅니다. 예를 들어 Solaris는 x86 및 SPARC에서 실행됩니다. 언급했듯이 Mac OSX는 PPC 및 x86에서 실행됩니다. Linux는 직장에서 사용하는 System z부터 작은 손목 시계 에 이르기까지 모든 것을 실행합니다 .

CPU가 모든 종류의 선택을 제공하는 경우 OS에서 사용하는 ABI / 호출 규칙은 코드가 다른 모든 사람의 코드를 호출하도록하려면 어떤 선택을해야하는지 지정합니다.

프로세서와 방향은 다음과 같습니다.

  • x86 : 다운.
  • SPARC : 선택 가능. 표준 ABI는 다운을 사용합니다.
  • PPC : 다운 된 것 같습니다.
  • System z : 링크 된 목록에서 나는 당신을 농담합니다 (그러나 적어도 zLinux에서는 여전히 다운 됨).
  • ARM : 선택 가능하지만 Thumb2에는 아래로만 압축 인코딩이 있습니다 (LDMIA = 이후 증가, STMDB = 이전 감소).
  • 6502 : 다운 (단 256 바이트).
  • RCA 1802A : SCRT 구현에 따라 원하는 방식으로.
  • PDP11 : 아래로.
  • 8051 : 위로.

지난 몇 명에서 내 나이를 보여주는 1802는 초기 셔틀을 제어하는 ​​데 사용 된 칩 (문이 열려 있는지 감지, 처리 능력을 기반으로 생각합니다.)과 두 번째 컴퓨터 인 COMX-35 ( 내 ZX80 다음 ).

PDP11 세부 사항은 여기에서 , 8051 세부 사항은 여기 에서 수집했습니다 .

SPARC 아키텍처는 슬라이딩 윈도우 레지스터 모델을 사용합니다. 구조적으로 볼 수있는 세부 정보에는 유효하고 내부적으로 캐시 된 레지스터 창의 순환 버퍼도 포함되며 오버플로 / 언더 플로시 트랩이 포함됩니다. 자세한 내용은 여기 를 참조 하십시오 . SPARCv8 설명서에서 설명하는 것처럼 SAVE 및 RESTORE 명령어는 ADD 명령어와 레지스터 창 회전과 같습니다. 일반적인 음수 대신 양수 상수를 사용하면 스택이 증가합니다.

앞서 언급 한 SCRT 기술은 또 다른 기술입니다. 1802는 SCRT (표준 호출 및 반환 기술)를 위해 일부 또는 16 비트 레지스터를 사용했습니다. 하나는 프로그램 카운터 였는데, 어떤 레지스터 든 SEP Rn명령어 와 함께 PC로 사용할 수 있습니다 . 하나는 스택 포인터이고 두 개는 항상 SCRT 코드 주소를 가리 키도록 설정되었습니다. 하나는 호출 용이고 다른 하나는 리턴 용입니다. 특별한 방식으로 처리 된 레지스터는 없습니다 . 이러한 세부 사항은 기억에서 가져온 것이며 완전히 정확하지 않을 수 있습니다.

예를 들어 R3이 PC이고 R4가 SCRT 호출 주소이고 R5가 SCRT 반환 주소이고 R2가 "스택"(소프트웨어에서 구현 된 따옴표)이면 SEP R4R4를 PC로 설정하고 SCRT 실행을 시작합니다. 통화 코드.

그 다음은 R2 "스택"(나는 R6이 임시 저장을 위해 사용되었다고 생각)에 R3 저장 위치를 조정하거나 아래로, R3 다음과 같은 2 바이트를 잡아, 그들을로드합니다 으로 다음 수행 R3 SEP R3및 새 주소에서 실행.

반환하려면 SEP R5R2 스택에서 이전 주소를 가져 오고 여기에 2 개를 추가하고 (호출의 주소 바이트를 건너 뛰기 위해) R3에로드 SEP R3하고 이전 코드 실행을 시작합니다.

모든 6502 / 6809 / z80 스택 기반 코드 이후 처음에는 머리를 감싸기가 매우 어렵지만 벽에 머리를 대는 방식에서는 여전히 우아합니다. 또한 칩의 가장 큰 판매 기능 중 하나는 16 비트 레지스터의 전체 제품군이었습니다 (SCRT의 경우 5 개, DMA의 경우 2 개, 메모리에서 인터럽트). 아, 현실보다 마케팅의 승리 :-)

System z는 실제로 호출 / 반환에 R14 및 R15 레지스터를 사용하여 매우 유사합니다.


3
목록에 추가하기 위해 ARM은 어느 방향 으로든 성장할 수 있지만 특정 실리콘 구현에 의해 둘 중 하나로 설정 될 수 있습니다 (또는 소프트웨어에서 선택 가능하도록 둘 수 있음). 내가 다룬 소수는 항상 성장 모드였습니다.
Michael Burr

1
지금까지 본 ARM 세계 (ARM7TDMI)의 작은 부분에서 스택은 전적으로 소프트웨어로 처리됩니다. 반환 주소는 필요한 경우 소프트웨어에 의해 저장되는 레지스터에 저장되며 사전 / 사후 증가 / 감소 명령을 사용하면 스택에이 주소와 다른 항목을 어느 방향 으로든 넣을 수 있습니다.
starblue

1
하나는 HPPA, 스택은 자랐습니다! 합리적으로 현대적인 건축물 중에서 상당히 드뭅니다.
tml 2011-06-05

2
궁금한 사람을 위해 z / OS에서 스택이 작동하는 방식에 대한 좋은 리소스가 있습니다. www-03.ibm.com/systems/resources/Stack+and+Heap.pdf
Dillon Cower 2013 년

1
이해해 주셔서 감사합니다 @paxdiablo. 때때로 사람들은 당신이 그러한 말을 할 때, 특히 그것이 나이가 많은 말을 할 때 개인적인 모욕으로 받아들입니다. 나는 과거에 같은 실수를 저질렀 기 때문에 차이가 있다는 것을 압니다. 조심해.
CasaDeRobison

23

C ++에서 (C에 적용 가능) stack.cc :

static int
find_stack_direction ()
{
    static char *addr = 0;
    auto char dummy;
    if (addr == 0)
    {
        addr = &dummy;
        return find_stack_direction ();
    }
    else
    {
        return ((&dummy > addr) ? 1 : -1);
    }
}

14
와, "auto"키워드를 본 지 오래되었습니다.
paxdiablo

9
(& dummy> addr)은 정의되지 않았습니다. 두 포인터를 관계 연산자에 공급 한 결과는 두 포인터가 동일한 배열 또는 구조를 가리키는 경우에만 정의됩니다.
sigjuice

2
C / C ++에서 전혀 지정하지 않는 스택의 레이아웃을 조사하는 것은 처음에는 "이동할 수 없습니다". 그래서 나는 그것에 대해 정말로 신경 쓰지 않을 것입니다. 이 기능은 한 번만 올바르게 작동하는 것 같습니다.
ephemient

9
이를 static위해 를 사용할 필요가 없습니다 . 대신 주소를 재귀 호출에 대한 인수로 전달할 수 있습니다.
R .. GitHub STOP HELPING ICE

5
플러스하는을 사용하여 static, 당신이 한 번 이상이를 호출하는 경우, 후속 호출은 ... 실패 할 수 있습니다
크리스 도드

7

성장하는 것의 장점은 스택이 일반적으로 메모리의 맨 위에 있었다는 오래된 시스템에서입니다. 프로그램은 일반적으로 맨 아래부터 시작하여 메모리를 채웠으므로 이러한 종류의 메모리 관리로 인해 스택 맨 아래를 적절한 곳에 측정하고 배치 할 필요성이 최소화되었습니다.


3
'장점'이 아니라 정말 팽팽하게.
Marquis of Lorne

1
팽팽함이 아닙니다. 요점은 @valenok이 지적했듯이 두 개의 증가하는 메모리 영역이 방해하지 않는 것입니다 (어쨌든 메모리가 가득 차지 않는 한).
YvesgereY

6

스택은 x86에서 감소합니다 (아키텍처에 의해 정의 됨, pop 증가 스택 포인터, 푸시 감소).


5

MIPS 및 많은 최신 RISC 아키텍처 (예 : PowerPC, RISC-V, SPARC ...)에는 pushpop지침 이 없습니다 . 이러한 작업은 스택 포인터를 수동으로 조정 한 다음 조정 된 포인터에 상대적으로 값을로드 / 저장하여 명시 적으로 수행됩니다. 모든 레지스터 (제로 레지스터 제외)는 범용이므로 이론적으로 모든 레지스터 는 스택 포인터가 될 수 있으며 스택 은 프로그래머가 원하는 모든 방향으로 성장할 수 있습니다.

즉, 스택은 일반적으로 대부분의 아키텍처에서 성장하여 스택과 프로그램 데이터 또는 힙 데이터가 증가하여 서로 충돌하는 경우를 방지하기 위해 증가합니다. sh-의 대답에 대해 언급 한 훌륭한 주소 지정 이유도 있습니다 . 몇 가지 예 : MIPS ABI는 아래쪽으로 성장 하고 스택 포인터로 $29(AKA $sp)를 사용하고, RISC-V ABI는 아래쪽으로 성장하고 x2를 스택 포인터로 사용합니다.

Intel 8051에서는 메모리 공간이 너무 작아서 (원래 버전에서는 128 바이트) 힙이 없어서 스택이 늘어나고 힙이 늘어나는 것과 분리되도록 스택을 맨 위에 놓을 필요가 없기 때문에 스택이 커집니다. 바닥에서

https://en.wikipedia.org/wiki/Calling_convention 에서 다양한 아키텍처의 스택 사용에 대한 자세한 정보를 찾을 수 있습니다.

또한보십시오


2

내가 볼 수있는 한이 점을 건드리지 않은 다른 답변에 약간의 추가 사항이 있습니다.

스택이 아래쪽으로 커지면 스택 내의 모든 주소가 스택 포인터에 상대적인 양의 오프셋을 갖게됩니다. 사용되지 않은 스택 공간 만 가리 키기 때문에 음수 오프셋이 필요하지 않습니다. 이는 프로세서가 스택 포인터 관련 주소 지정을 지원할 때 스택 위치에 대한 액세스를 단순화합니다.

많은 프로세서에는 일부 레지스터에 상대적인 양수 전용 오프셋으로 액세스를 허용하는 명령이 있습니다. 여기에는 많은 현대 건축과 오래된 건축이 포함됩니다. 예를 들어 ARM Thumb ABI는 단일 16 비트 명령어 내에서 인코딩 된 양의 오프셋을 사용하여 스택 포인터 상대 액세스를 제공합니다.

스택이 위로 커지면 스택 포인터와 관련된 모든 유용한 오프셋은 음수가되어 덜 직관적이고 덜 편리합니다. 또한 구조체의 필드에 액세스하기위한 레지스터 관련 주소 지정의 다른 응용 프로그램과도 상충됩니다.


2

대부분의 시스템에서 스택은 성장하고 https://gist.github.com/cpq/8598782의 내 기사 는 왜 성장하는지 설명합니다. 간단합니다. 증가하는 두 개의 메모리 블록 (힙 및 스택)을 고정 된 메모리 청크에 배치하는 방법은 무엇입니까? 가장 좋은 해결책은 그것들을 반대쪽 끝에 놓고 서로를 향해 자라게하는 것입니다.


그 요지는 이제 죽은 것 같습니다 :(
Ven

@Ven-나는 그것을 얻을 수있다
Brett Holman

1

프로그램에 할당 된 메모리에는 "영구 데이터"즉, 맨 아래에 프로그램 자체에 대한 코드가 있고 중간에 힙이 있기 때문에 크기가 줄어 듭니다. 스택을 참조 할 또 다른 고정 포인트가 필요하므로 맨 위에 있습니다. 이는 스택이 힙의 오브젝트에 잠재적으로 인접 할 때까지 아래로 커짐을 의미합니다.


0

이 매크로는 UB없이 런타임에이를 감지해야합니다.

#define stk_grows_up_eh() stk_grows_up__(&(char){0})
_Bool stk_grows_up__(char *ParentsLocal);

__attribute((__noinline__))
_Bool stk_grows_up__(char *ParentsLocal) { 
    return (uintptr_t)ParentsLocal < (uintptr_t)&ParentsLocal;
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.