구현 할당
D, C, C++와 같은 언어에서 인라인 x86 어셈블러를 사용하여 alloca()를 어떻게 구현합니까?약간 수정된 버전을 만들고 싶은데 우선 표준 버전이 어떻게 구현되는지 알아야 합니다.컴파일러에서 분해를 읽는 것은 매우 많은 최적화를 수행하기 때문에 도움이 되지 않으며, 저는 단지 표준 양식을 원합니다.
편집: 어려운 점은 이것이 정상적인 함수 호출 구문, 즉 네이키드 함수 같은 것을 사용하여 정상적인 alloca()처럼 보이게 하기를 원한다는 것입니다.
편집 #2: 아, 대체 뭐야, 프레임 포인터를 빠뜨린 게 아닐 거라고 가정해도 됩니다.
alloca실제로 컴파일러 지원이 필요합니다.여기 있는 몇몇 사람들은 이렇게 쉽다고 말합니다.
sub esp, <size>
불행하게도 사진의 반밖에 안되는 사진입니다.예, "스택에 공간을 할당"할 수 있지만 몇 가지 문제가 있습니다.
했다면,
espebp(프레임 포인터 없이 컴파일하는 경우 typical).그러면 그 참고 자료들을 조정할 필요가 있습니다.프레임 포인터를 사용하더라도 컴파일러들이 이 작업을 수행하는 경우도 있습니다.한 것은 은, , 으로
alloca함수가 종료되면 "free" 상태가 되어야 합니다.
큰 것은 2번 포인트입니다.대칭적으로 추가하려면 컴파일러가 코드를 방출해야 하기 때문입니다.<size>.esp기능의 모든 출구에서.
가장 가능성 있는 경우는 컴파일러가 컴파일러에게 필요한 도움을 요청할 수 있는 몇 가지 고유한 기능을 제공한다는 것입니다.
편집:
실제로 glibc(GNU 의 libc 구현) 에서.현의 .alloca이는 단순히 다음과 같습니다.
#ifdef __GNUC__
# define __alloca(size) __builtin_alloca (size)
#endif /* GCC. */
편집:
그것에 대해 생각한 후에, 내가 생각하는 최소한의 필요는 컴파일러가 항상 다음을 사용하는 함수에서 프레임 포인터를 사용하는 것일 것입니다.alloca. 하면다를 모든 주민을 할 수 .ebp하게 프레임 할 수 .esp.
편집:
그래서 이런 실험을 해봤습니다.
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define __alloca(p, N) \
do { \
__asm__ __volatile__( \
"sub %1, %%esp \n" \
"mov %%esp, %0 \n" \
: "=m"(p) \
: "i"(N) \
: "esp"); \
} while(0)
int func() {
char *p;
__alloca(p, 100);
memset(p, 0, 100);
strcpy(p, "hello world\n");
printf("%s\n", p);
}
int main() {
func();
}
유감스럽게도 제대로 작동하지 않습니다.조립 출력을 gcc별로 분석한 후.최적화가 방해가 되는 것 같습니다.문제는 컴파일러의 옵티마이저가 내 인라인 어셈블리를 전혀 인식하지 못하기 때문에 예상치 못한 순서로 작업을 수행하고 여전히 다음을 통해 참조하는 습관이 있다는 것입니다.esp.
ASM 결과는 다음과 같습니다.
8048454: push ebp
8048455: mov ebp,esp
8048457: sub esp,0x28
804845a: sub esp,0x64 ; <- this and the line below are our "alloc"
804845d: mov DWORD PTR [ebp-0x4],esp
8048460: mov eax,DWORD PTR [ebp-0x4]
8048463: mov DWORD PTR [esp+0x8],0x64 ; <- whoops! compiler still referencing via esp
804846b: mov DWORD PTR [esp+0x4],0x0 ; <- whoops! compiler still referencing via esp
8048473: mov DWORD PTR [esp],eax ; <- whoops! compiler still referencing via esp
8048476: call 8048338 <memset@plt>
804847b: mov eax,DWORD PTR [ebp-0x4]
804847e: mov DWORD PTR [esp+0x8],0xd ; <- whoops! compiler still referencing via esp
8048486: mov DWORD PTR [esp+0x4],0x80485a8 ; <- whoops! compiler still referencing via esp
804848e: mov DWORD PTR [esp],eax ; <- whoops! compiler still referencing via esp
8048491: call 8048358 <memcpy@plt>
8048496: mov eax,DWORD PTR [ebp-0x4]
8048499: mov DWORD PTR [esp],eax ; <- whoops! compiler still referencing via esp
804849c: call 8048368 <puts@plt>
80484a1: leave
80484a2: ret
보다시피, 그것은 그리 간단하지 않습니다.유감스럽게도, 저는 당신이 컴파일러 지원이 필요하다는 저의 원래 주장을 지지합니다.
이렇게 하는 것은 어려울 것입니다. 사실 컴파일러의 코드 생성을 충분히 제어하지 않는 한 완전히 안전하게 수행될 수는 없습니다.루틴은 스택이 반환될 때 모든 것이 정리되도록 스택을 조작해야 하지만 스택 포인터는 메모리 블록이 그 위치에 유지되도록 유지됩니다.
문제는 함수 호출 전체에 걸쳐 스택 포인터가 수정되었음을 컴파일러에 알릴 수 없는 한 스택 포인터를 통해 다른 로컬(또는 무엇이든)을 계속 참조할 수 있다고 결정할 수 있지만 오프셋이 부정확해질 수 있다는 것입니다.
D 프로그래밍 언어의 경우 alloca()의 소스 코드가 다운로드와 함께 제공됩니다.그것이 어떻게 작동하는지는 꽤 잘 언급되어 있습니다.dmd1의 경우 /dmd/src/phobos/internal/alloca.d에 있습니다.dmd2의 경우 /dmd/src/druntime/src/compiler/dmd/alloca.d에 있습니다.
에는 C C++ 가 되어 있지 alloca()alloca()C 또는 C++ 표준(또는 해당 사항의 경우 POSIX) ¹에 없습니다.
는 를 할 수도 .alloca()산더미를 이용해서 예를 RVCT)예를 들어인 ARM RealView(RVCT) 예를 들어alloca()사용하다malloc()버퍼를 할당하고(여기 웹사이트에서 referen), 함수가 반환될 때 컴파일러가 버퍼를 해제하는 코드를 내보내도록 합니다.이것은 스택 포인터를 가지고 놀 필요는 없지만 컴파일러 지원이 필요합니다.
Microsoft Visual C++에는 스택에 공간이 충분하지 않으면 힙을 사용하는 기능이 있지만 호출자가 사용해야 합니다._freea()는 다르게,_alloca()인 자유를 .는 을 원합니다.
수 도 당연히 를 할 수 의 식 에서 로컬 수 없기 에 (C++ destructor) 를 것 같습니다.alloca()RAI를 사용하는 매크로.다시 말하지만, 당신은 분명히 사용할 수 없는 것 같습니다.alloca()(함수 매개변수와 같은) 몇몇 표현식에서 말입니다.)
네, 작성은 입니다.alloca()입니다라고 system("/usr/games/nethack").
연속 통과 스타일 할당
순수 ISO C++의 가변 길이 배열.개념 증명 구현.
사용.
void foo(unsigned n)
{
cps_alloca<Payload>(n,[](Payload *first,Payload *last)
{
fill(first,last,something);
});
}
핵심 아이디어
template<typename T,unsigned N,typename F>
auto cps_alloca_static(F &&f) -> decltype(f(nullptr,nullptr))
{
T data[N];
return f(&data[0],&data[0]+N);
}
template<typename T,typename F>
auto cps_alloca_dynamic(unsigned n,F &&f) -> decltype(f(nullptr,nullptr))
{
vector<T> data(n);
return f(&data[0],&data[0]+n);
}
template<typename T,typename F>
auto cps_alloca(unsigned n,F &&f) -> decltype(f(nullptr,nullptr))
{
switch(n)
{
case 1: return cps_alloca_static<T,1>(f);
case 2: return cps_alloca_static<T,2>(f);
case 3: return cps_alloca_static<T,3>(f);
case 4: return cps_alloca_static<T,4>(f);
case 0: return f(nullptr,nullptr);
default: return cps_alloca_dynamic<T>(n,f);
}; // mpl::for_each / array / index pack / recursive bsearch / etc variacion
}
alloca는 조립 코드에 직접 구현됩니다.상위 언어에서 스택 레이아웃을 직접 제어할 수 없기 때문입니다.
또한 대부분의 구현에서는 성능상의 이유로 스택을 정렬하는 것과 같은 추가적인 최적화 작업을 수행합니다.X86에서 스택 공간을 할당하는 표준 방법은 다음과 같습니다.
sub esp, XXX
반면 XXX는 모두 코팅할 바이트 수입니다.
:
구현을 검토하려면(MSVC를 사용하는 경우) alloca16.asm 및 chkstk.asm을 참조하십시오.
첫 번째 파일의 코드는 기본적으로 원하는 할당 크기를 16바이트 경계로 정렬합니다.두 번째 파일의 코드는 실제로 새 스택 영역에 속하는 모든 페이지를 걸어 터치합니다.그러면 OS에서 스택을 확장하는 데 사용되는 PAGE_GAURD 예외가 트리거될 수 있습니다.
오픈 왓콤과 같은 오픈 소스 C 컴파일러의 소스를 조사하여 직접 찾을 수 있습니다.
c99의 가변 길이 배열을 사용할 수 없는 경우 복합 리터럴 캐스트를 사용하여 포인터를 피할 수 있습니다.
#define ALLOCA(sz) ((void*)((char[sz]){0}))
이것은 (gcc 확장으로) -ansi에도 적용되며 함수 인수인 경우에도 적용됩니다.
some_func(&useful_return, ALLOCA(sizeof(struct useless_return)));
단점은 c++++>4.6으로 컴파일하면 오류가 발생한다는 것입니다. 임시 배열의 주소를 사용하는 것... clang과 icc는 불평하지 않습니다.
할당은 간단합니다. 스택 포인터를 위로 이동한 다음 이 새 블록을 가리킬 모든 읽기/쓰기를 생성합니다.
sub esp, 4
우리가 하고 싶은 것은 바로 이런 것입니다.
void* alloca(size_t size) {
<sp> -= size;
return <sp>;
}
Assembly(Visual Studio 2017, 64bit)에서 다음과 같이 나타납니다.
;alloca.asm
_TEXT SEGMENT
PUBLIC alloca
alloca PROC
sub rsp, rcx ;<sp> -= size
mov rax, rsp ;return <sp>;
ret
alloca ENDP
_TEXT ENDS
END
안타깝게도 반환 포인터는 스택의 마지막 항목이므로 덮어쓰기를 원하지 않습니다.추가적으로 우리는 8의 배수까지 둥근 크기의 정렬을 관리해야 합니다.그래서 우리는 이것을 해야 합니다.
;alloca.asm
_TEXT SEGMENT
PUBLIC alloca
alloca PROC
;round up to multiple of 8
mov rax, rcx
mov rbx, 8
xor rdx, rdx
div rbx
sub rbx, rdx
mov rax, rbx
mov rbx, 8
xor rdx, rdx
div rbx
add rcx, rdx
;increase stack pointer
pop rbx
sub rsp, rcx
mov rax, rsp
push rbx
ret
alloca ENDP
_TEXT ENDS
END
my_alloca: ; void *my_alloca(int size);
MOV EAX, [ESP+4] ; get size
ADD EAX,-4 ; include return address as stack space(4bytes)
SUB ESP,EAX
JMP DWORD [ESP+EAX] ; replace RET(do not pop return address)
입력 방법을 권장합니다.286 이상의 프로세서에서 사용할 수 있습니다(186에서도 사용할 수 있었을지도 모르지만, 즉시 기억할 수는 없지만, 어쨌든 널리 사용할 수는 없었습니다.
언급URL : https://stackoverflow.com/questions/714692/alloca-implementation
'programing' 카테고리의 다른 글
| Linux에서 파일을 복사하는 가장 효율적인 방법 (0) | 2023.09.24 |
|---|---|
| jQuery를 사용하여 요소의 CSS 속성을 해제하려면 어떻게 해야 합니까? (0) | 2023.09.24 |
| 여러 열을 하나로 결합하고 사용자 지정 문자열로 형식을 지정하는 방법은 무엇입니까? (0) | 2023.09.24 |
| Oracle에서 날짜를 초 단위로 자르는 방법 (0) | 2023.09.24 |
| 테이블 생성 시 Django MySQL 오류 (0) | 2023.09.24 |