[C]변수들이 스텍에 쌓이는 순서
변수 선언의 순서에 따라 스텍에 쌓이는 순서가 달라집니다.
strcpy()함수와 같이 메모리를 신경써야되는 함수들은 실수할 가능성이 큽니다. 조금이나마 피해를 줄이기 위해서는 실수할 가능성이 큰 변수의 주소를 뒤쪽에 위치시키는 것이 안전할 것입니다. 이런 것들을 자세히 알아보기 위해서는 어셈블리어를 보는 것이 좋겠지만 오늘은 간단히 코딩만으로 알아볼 계획입니다. 먼저 다음과 같은 조건에서 하였습니다.
- 64비트 운영체제 Vscode
- ASLR기능을 껐습니다.(ASLR기능이란 보안목적으로 매번호출할때마다 주소값을 바뀌게하는 기능입니다.)
#include <stdio.h>
#include <string.h>
void sss(void)
{
char ss[4];
}
int main(void)
{
char word[4];
char word1[4];
char *address;
const char num[] = "123456789";
strcpy(word, num); // 고의로 메모리초과
sss(); // sss()함수 호출
/* 출력함수 생략 */
address = word; // word의 주소 address주소로 지정
}
변수명 | 주소 |
---|---|
address | 38373635(모름) |
… | … |
포인터임시주소 | 0x0061FECC |
word[4] | 0x0061FEC8 |
word1[4] | 0x0061FEC4 |
num[] | 0x0061FEBA |
… | … |
ss[4] | 0x0061FE8C |
배열의 경우 선언순서에 따라 위쪽부터 차례로 스텍에 쌓였습니다. 그러나 포인터 같은 경우 스텍의 가장 위쪽에 임시로 4바이트씩 쌓였습니다(포인터의 크기:4바이트 기준). 단, 포인터를 선언만하고 사용하지않으면 스텍에 쌓이지 않았습니다.
프로그램실행시 가장먼저 main함수의 지역변수들이 스택 위쪽에 쌓였으며 외부함수의 변수는 아래쪽에 자리함을 알 수 있습니다.
여기서 포인터 주소값을 명시적으로 해주면 포인터변수 또한 선언순서에 따라 스텍에 쌓이게 할 수 있습니다.
1. "&"를 사용하여 포인터 주소를 사용하여 포인터의 주소를 명시적으로 바꿔보기
printf("address포인터의 주소는 %s입니다.\n", &address); // &address를 사용해서 포인터의 주소를 확인
위와 같은 코드를 추가해서 *address
의 주소를 확인하는 것만으로 다음과같이 스텍에 쌓이게 됬습니다.
하지만 이러한 방법을 의도적으로 사용하기 보다는 포인터의 주소가 명확하지 않을때는 가장위에 임시스텍으로 쌓인다는 것 정도로만 아는 것이 좋을 것 같습니다.
변수명 | 주소 |
---|---|
… | … |
word[4] | 0x0061FECC |
word1[4] | 0x0061FEC8 |
*address | 0x0061FEC4 |
num[] | 0x0061FE8C |
… | … |
ss[4] | 0x0061FE8C |
2. 포인터배열로 선언
char *address[2]; //char*형 주소를 2개 담을 수 있는 배열
이 처럼 포인터배열로 선언하여 사용하면 됩니다. 위의 포인터배열은 크기가 8바이트(포인터원소 2개)로 잡히게 됩니다.
고찰
- 바뀔가능성이 큰 변수, 포인터변수, const 변수 순으로 선언하면 실수하더라도 스텍충돌을 줄일 수 도 있을 것 입니다.
- 가장중요한 것은 정해진 메모리만 사용하는 것 입니다.
- 안전한 상황이 아닐때는 strcpy, memcpy와 같은 함수보다 strlcpy, memmove와 같은 함수를 사용하는 것이 좋을 것 입니다.
- 다른운영체제 다른콘솔프로그램에서는 다른방식으로 스텍에 쌓일 수 있습니다. (c가 크로스 플랫폼이라는 주장은 컴파일 전 단계까지! 어셈블리어로 만드는 과정은 컴파일러 마음..)
- 하지만 요즘 컴파일러들의 ASLR기능이 잘 발달되어 있어서 크게 신경을 쓰지않아도 될 것 같습니다.