https://www.youtube.com/watch?v=nRR0ymmICBo&list=PLz--ENLG_8TMdMJIwyqDIpcEOysvNoonf&index=11
저번에 포스팅 했던 개념을 잠깐 가져와 보자면 자료형과는 상관없이 포인터는 항상 같은 용량을 차지한다는 것을 알 수 있었다.
결국
int *
를 입력하면 시스템을 타는 것이다.
내가 만약 32bit 컴퓨터를 쓴다면 4byte가 될 것이고
지금처럼 64bit 컴퓨터를 쓴다면 8byte가 될 것이다. 앞에 자료형이 int던 double이던 너구리던 뭐던 상관이 없다.
그러면 용량이 같은 것은 이해가 되는데 그럼 자료형을 나누지 앟고 해도 상관이없는거 아니냐 할 수 있는데 그것은 다음 포스팅때 다루겠다.
여기 까지가 저번 내용이었다.
→ 요약.
자료형에 따라서 크기가 달라지지도 않는데 그러면 자료형 왜 쓰냐?
배열에 대해서.
배열에 대해서 알 부분이 있다.
#include <stdio.h>>
int main() {
int a;
int * pa;
int array[1];
pa = &a;
*pa = 0x77;
array[0] = 0x55;
return 1;
}
이런 코드가 있다고 해보자. 이 c문법을 살짝 뜯어서 바라보면 아래와 같이 나온다.
저게 뭐지? 싶을 수도 있는데 나중에 다룰 어셈블리라는 것이다.
C보다 더 low level의 언어이다.
각 주소마다 명령어와 값들이 저장되어 있다. 몇가지 예시를 들자면
*pa = 0x77; == $0x77,(%rax)
array[0]=0x55; == $0x55,-0xc(%rbp)
왼쪽과 오른쪽은 서로 같은 말이다. 그리고 *(포인터) 부분은 괄호 부분이 담당하고 있다. 옆에 있는 곳에서 rbp라는 것은 스택인데 이 rbp에서
rbp - 28 == a
rbp - 24 == pa
rbp - 12 == array
를 가르킨다.
스택 시작 주소를 기점으로 얼마를 빼서 그곳에 값을 저장하는 형태로 사용한다. 지금은 자세하게 설명은 안하겠다.
결국은 여기서 말하고 싶은 것은 내부적으로 포인터의 작동 원리를 말한다.
만약에 배열을 5개를 지정하고 3번 배열에 접근하고 싶다면?
#include <stdio.h>>
int main() {
int array[5];
array[3] = 0x55;
*(array + 3) = 0x77;
return 1;
}
위 코드를 어셈블리로 따면 다음과 같이 나온다.
이 부분을 잘 봐보자. 해당 부분은 아래 코드 부분이다.
array[3] = 0x55;
*(array + 3) = 0x77;
주소는 다르지만 그 다음 부분이 위 아래 모두 같다는 것을 볼 수 있다. 또 다른것을 실험해보자.
#include <stdio.h>>
int main() {
int array[5];
array[3] = 0x55;
*(array + 3) = 0x77;
char array_c[5];
array_c[3] = 0x44;
*(array_c + 3) = 0x99;
return 1;
}
마찬가지로 메모리 주소는 다르지만 뒤에 나오는 결과가 같다.
중요한것은 위의 코드를 볼때 int형 배열에서도 +3을 했고 char형 배열에서도 +3을 했다는 것이다.
int 배열 5개와 char형 배열 5개를 선언한 것을 그림으로 표현하면 아래와 같다.
패드가 없어서 그림이 이상한건 이해해주길…
주소가 int형은 4, char형은 1byte씩 증가하는 것을 이제 알 것이다. (각각 4, 1바이트씩 용량을 차지함) 진짜 그런지 코드로 확인해보자.
#include <stdio.h>
int main() {
int array[5];
*(array + 2) = 0x76;
*(array + 3) = 0x77;
*(array + 4) = 0x78;
char array_c[5];
*(array_c + 2) = 0x91;
*(array_c + 3) = 0x92;
*(array_c + 4) = 0x93;
return 1;
}
진짜 int형은 4씩, char형은 1씩 달라지는 것을 볼 수 있었다. (b = 11, a = 10) 어셈블리 코드를 딴 이유는 포인터나 배열이나 안으로 들어가면 똑같이 동작한다 는 것을 볼 수 있어서 그런 것이다.
만약에 컴파일러가 자료형에 따라 주소 값을 다르게 변경해주지 않게 된다면 정말 복잡해진다.
#include <stdio.h>
#define MAX 5
int main() {
int array[MAX];
for(int i = 0; i < MAX; i++){
array[i*4] = i; // 자료형에 따라 곱하는걸 달리 해줘야 한다.
}
return 1;
}
주석에 달아놓은 것 처럼 하나하나 사람이 자료형에 따라 바꿔 줘야하는 비극이 일어난다. 그래서 컴파일러가 알아서 내부적으로 해주는 것.
int * a;
char * b;
float * c;
double * d;
short * e;
그래서 위처럼 선언을 해주는 것이다. 포인터 앞에 자료형을 넣는 이유가 위에 내용과 같았다.
다른걸 한번 해보자.
#include <stdio.h>
int main() {
int a[10] = {0};
printf("a[3] = %d\\n", a[3]); // 1
swap(a);
printf("a[3] = %d\\n", a[3]); // 2
return 1;
}
int swap(int * pa){
*(pa + 3) = 99;
return 1;
}
//결과
1. a[3] = 0 // 1
2. a[3] = 99 // 2
먼저 swap개념을 설명하면 좋을 것 같다.
#include <stdio.h>
int swap(int b);
int main() {
int a = 7;
printf("a = %d\\n", a);
swap(a);
printf("a = %d\\n", a);
return 1;
}
int swap(int b){
printf("b : %d\\n", b);
b = 99;
printf("b : %d\\n", b);
return 1;
}
// 결과
a = 7
b : 7
b : 99
a = 7
엥? 마지막 a도 값이 변해야 할 것 같은데 변하지 않았다. 이 개념은 학부생 초기에 배우는 개념인데 좀 더 자세하게 뜯어서 알아보자.
스택!
메인 함수가 호출이 되고 그 안에
int a
int b
같은 변수들이 있다면 스택 공간 안에 메인함수가 필요한 공간을 할당한다. 그리고 스택 포인터라는 것을 통해 어느정도 공간을 쓰고 있는지 알수 있다.
어셈블리엔 rsp 와 rbp라는 것이 있다.
- rsp : 스택 포인터 레지스터
- rbp : 베이스 포인터 레지스터
이 베이스 포인터 레지스터를 기준으로 변수 a와 변수 b에 접근 한다는 것이다.
그림으로 보면
이런 식으로 rbp에서 -n을 하면서 각각의 값에 접근하는 방식이다. 그래서 어셈블리 코드에서 rbp를 기준으로 지역 변수에 접근하게 된다.
그런데 swap함수를 실행하면 무슨일이 일어나느냐?
swap함수를 호출하면 스택에 여러가지를 저장한다. 예를 들면 bp가 가르키고 있던 값도 저장하고
메인 함수의 호출되는 주소도 저장한다. 그러면 sp가 위로 올라가서 가르킨다.
→ swap함수를 호출할때 다시 main으로 넘어올 수 있는 정보를 스택에 저장한다.
swap함수가 끝났을때 다시 main으로 돌아와야 하니 스택에 위와 같은 정보를 저장한다. 그러면 자연스럽게 용량이 증가하니 그만큼 스택 포인터가 올라가고 베인스 포인터는 처음 그 자리를 그대로 가르키고 있다.
그 다음으로 swap함수 안에 이용할 변수를 할당해야 하니 그만큼의 스택 포인터는 올라가게 된다. 문제는 bp(베이스 포인터)의 위치가 바뀌어 버린다. 원래는 bp가 메인위치에 있었지만 지금은 swap함수안에 있는 변수를 가르키게 된다. 하지만 접근하는 방식은 똑같아 졌기 때문에 다른 변수에 접근한다.
처음 rbp -20 이 변수 a였다면 지금은 바뀐 위치 때문에 rbp - 20을 해도 메인에 있는 a가 아니라 swap 함수에 있는 a에 접근하는 식으로 변한다. 그렇다면 이론상 바뀐 rbp에 -50정도를 하면 main에 있는 a에 접근 할 수 있지 않을까? 하는 생각이 들 수 있다. 이론상 가능하지만 컴파일러가 그것을 못하게 막아버린것 같다. 그래서 아무리 함수 안에서 값을 변하게 해도 실제 값을 바꾸지는 못한다.
하지만 만약에 a에 있는 주소값을 swap함수 에 있는 b에 보냈다고 한다면 a 주소를 참조해서 실제 값을 변하게 할 수 있다. 이것에 swap 함수의 역할이다.
swap함수가 끝나면?
스택에 저장했던 swap함수를 바로 삭제하지는 않는다. 다만 sp가 내려갈 뿐이다. 그리고 복구에 필요한 정보에 접근하면 다시 main으로 sp가 되돌아오면서 bp도 다시 메인쯤을 가르킨다.
만약에 main에 또다른 함수 sum같은 것이 호출된다고 하면 다시 sp가 올라가면서 main으로 복구할 수 있는 정보들을 스택에 저장하고 기존에 남아있던 swap함수를 삭제시킨다.
더 알기 쉬운 정보.
'언어공부 > c' 카테고리의 다른 글
c 포인터 / 포인터 개념 부숴버리기😏 (1) | 2022.09.23 |
---|---|
리눅스로 c를 배워보자. (0) | 2022.09.12 |