들어가기에 앞서, 메모리에 대해 기본적으로 알아야할 것이 있다.
- 메모리는 바이트 단위로 나뉜다.
- 메모리 공간 한 칸의 크기는 1 바이트다.
- 각 바이트마다 주소가 지정되어 있다.
이제 C 언어에서 변수를 선언할 때 메모리 할당이 어떤식으로 일어나는지 알아보자.
변수의 메모리 할당 & 할당되는 방식
메모리 공간을 표현한다면 위의 표로 나타낼 수 있고, 각각의 칸은 주소(ex: 1024, 1025, 1026 ... 등)를 가진다. 이때 크기가 4 바이트인 int형 변수 a, b, c를 선언해보자.
int형 변수 a, b, c는 변수의 선언과 동시에 메모리 상 빈 부분에 할당이 된다.
이때 꼭 알아야할 것은 변수가 메모리 상에 할당되는 방법이다. 변수의 할당을 쉽게 설명하자면, 해당 변수의 자료형의 크기만큼의 빈 칸이 있다면, 그곳에 할당되는 방식이다. 또한, 나중에 할당되는 변수가 무조건 먼저 할당된 변수보다 뒤쪽 메모리 공간에 할당되지 않는다. 나중에 선언한 변수는 앞서 선언한 변수보다 메모리 상에서 위에 위치할 수도 있다. (이는 변수의 종류와 할당 방법에 따라 할당되는 부분이 힙 영역, 스택 영역, 데이터 영역으로 나뉘기 때문이다.)
한 가지를 더 살펴보면, 이미 할당된 변수 사이에 해당 자료형의 크기 이상의 공간만 있다면 비집고(?) 들어갈 수 있다. 예시를 통해 알아보자.
위의 사진을 보면 이미 할당된 변수들 사이에 한 칸의 빈 공간이 두 개가 있다. 이때 만약 char형 변수 (1 바이트)를 두 개 선언한다면
위와 같이 할당된 변수들 사이로 비집고(?) 들어간다. 즉, 할당할 수 있는 자리가 있다면 메모리 상에서 순서 상관 없이 할당된다는 것이다.
주소 연산자 &
& 연산자는 해당 변수의 시작 주소를 가져올 수 있다. 변수의 이름 앞에 &을 붙여주면 해당 변수의 시작 주소값이 반환된다. 이를 일반화 하면
&변수이름
이다. 쉽게 예시를 통해 알아보자.
각 메모리 공간마다 이런식으로 주소가 존재한다. 이때 주소 연산자 &를 사용한다면
&a ---> 1030
&b ---> 1034
&c ---> 1038
라는 값을 볼 수 있는데, 이것은 해당 변수의 메모리 상에서의 시작 주소값이라는 것을 알 수 있다.
💬 주소값을 표현하는 여러가지 방법?
메모리 주소값도 결국 숫자다. 그렇기 때문에 printf 함수에서 %d, %x 등 여러 형태로 표현할 수 있다.
배열의 메모리 할당 & 할당되는 방식
다음과 같이 배열을 선언한다고 가정해보자.
int aa[3] = { 10, 20, 30 };
그러면 메모리 공간은 이렇게 표현될 것이다.
이때 가장 중요한 점은, 위에서 다룬 변수의 할당과 배열의 할당은 큰 차이점이 있다는 것이다. 먼저 우리는 변수의 할당에서는 변수의 자료형의 크기 이상의 공간만 있다면 메모리 상의 아무 곳에서나 할당이 가능하다는 것을 살펴보았다. 그러나 배열은 다르다. 배열의 각각 원소들은 메모리 상에서 항상 기차처럼 이어져 있어야 한다. 즉, 각각의 원소들이 떨어진 장소에서 할당될 수 없다는 것이다. 그렇기 때문에 배열의 자료형의 크기 이상의 공간이 있더라도, 배열 전체의 크기보다 작다면 그곳에 할당될 수 없다.
또한 배열은 신기한 특징을 한 가지 가지고 있는데, 배열의 이름이 곧 전체 배열의 주소라는 것이다. 다시 말하자면 배열의 이름인 aa가 배열의 주소를 가리킨다는 것이다(즉, 배열의 이름 = 배열의 첫 번째 원소의 주소). 그렇기 때문에 배열 aa의 주소를 구할 때는 주소 연산자 &를 쓰지 않고 단순히 배열 이름인 aa로 표현한다. 위의 자료로 예를 든다면
aa ---> 1031
인것이다.
#include <stdio.h>
#include <string.h>
int main(void)
{
int aa[3] = {10, 20, 30};
printf("aa = %d", aa);
return 0;
}
결과
aa = 2016504512
배열 이름의 활용법
- 배열이름 + n (n은 정수)
배열이 다음과 같이 할당되어 있다고 가정하자.
우리는 이때 배열이름 + n 을 했을 때 어떤 결과가 일어나는지 살펴볼 것이다.
위에서 배열이름은 시작 주소를 가리키기 때문에 만약 aa+1 이라고 했을 때 1031 + 1 = 1032 라는 결과를 생각할 수 있을 것이다. 하지만 이것은 틀린 결과다. 배열 이름에 정수 n을 더하고 뺀다면, 결과값이 '메모리 주소 + n'이 되는 것이 아니라, 해당 원소의 다음 원소의 시작 주소가 된다. 즉 aa+1 = 1031 + 4(int의 크기. 자료형에 따라 달라짐) = 1035인 것이다. 쉽게 설명하자면 배열이름 + n을 한다면 배열의 n번째 원소의 시작 주소를 나타낸다고 보면 된다. 이것을 일반화하면
배열이름 + n = &배열이름[n]
이다.
#include <stdio.h>
#include <string.h>
int main(void)
{
int aa[3] = {10, 20, 30};
// aa[n] = aa + n 이라는 것을 알 수 있다.
printf("&aa[0] = %d\t aa+0 = %d\n", &aa[0], aa+0);
printf("&aa[1] = %d\t aa+1 = %d\n", &aa[1], aa+1);
printf("&aa[2] = %d\t aa+2 = %d\n", &aa[2], aa+2);
return 0;
}
결과
&aa[0] = 207359524 aa+0 = 207359524
&aa[1] = 207359528 aa+1 = 207359528
&aa[2] = 207359532 aa+2 = 207359532
💬 그럼 배열이름 + n 과 &배열이름[n] 은 무슨 차이?
차이라고 할 것은 딱히 없고, 주소를 나타내는 표현 방식이 두 개가 있다고 생각하면 된다.
주소를 배열 첨자(인덱스)로 표현 | 주소를 배열 이름으로 표현 | 실제 주소 |
&aa[0] | aa ( + 0 ) | 1031 |
&aa[1] | aa + 1 | 1035 |
&aa[2] | aa + 2 | 1039 |
'Programming > C' 카테고리의 다른 글
포인터 (0) | 2021.06.16 |
---|---|
문자열과 관련 함수 (0) | 2021.05.02 |