[C언어] 메모리와 포인터

jaemin·2021년 5월 2일
0

C언어

목록 보기
1/2
post-thumbnail

1. 메모리와 포인터

변수의 본질은 메모리이며, 모든 메모리는 자신의 위치를 식별하기 위한 근거로 고유번호(일련번호)를 갖는데, 이 번호를 메모리의 주소라 합니다.
32비트 응용프로그램에서 1바이트 단위 메모리에 부여된 일련번호는 부호가 없는 32비트 정수이며, 보통 16진수로 표기합니다.

변수를 이루는 세 가지 요소는 다음과 같다.

  1. 이름이 부여된 메모리
  2. 그 안에 담긴 정보
  3. 메모리의 주소

우리가 보통 연산할때, 메모리에 저장된 정보를 사용합니다. 그런데, 어떤 연산자는 변수의 이름 즉, 메모리 자체에 관심이 있기도 합니다. 그 중 대표적인 것이 단항 연산자인 주소 번지 연산자(주소 연산자)입니다.

#include <stdio.h>

int main(void)
{
	int nData = 10;
    printf("%s\n", "nData");
    
    printf("%d\n", nData);
    printf("%p\n", &nData);
    
    return 0;
}

출력된 결과로 예제를 요약하면,

이름이 nData인 부호가 있는 32비트 정수형 메모리의 실제 주소는 0X0012FF28이고, 그 안에 저장된 정보를 해석하면 10진 정수인 10이다.

만약 포인터를 잘 다루고 싶다면, 의도적으로 이 문장처럼 말하는 습관이 필요합니다.

1.1 메모리의 종류

메모리는 용도에 따라 스택(stack), 힙(heap), 데이터 영역(data section), 텍스트 영역(text section) 등으로 나뉩니다.

분류 특징
Stack 자동변수이고 지역변수인 변수가 사용하는 메모리 영역이며, 임시 메 모리의 성격을 가진다.
크기가 작고 관리(할당 및 반환)가 자동으로 이루어지는 장점이 있 다.
Heap 동적 할당할 수 있는 자유 메모리 영역이며, 개발자 자신 스스로 직접 관리해야 한다
32비트 응용프로그램의 경우, 대략 1.xGB 정도를 사용할 수 있다.
따라서 대량의 메모리가 필요하거나 필요한 메모리의 크기를 미리 알 수 없을 때 사용한다.
Text section C 언어의 소스코드가 번역된 기계어가 저장된 메모리 영역이며, 기본적으로는 읽기 전용 메모리이다.
Data section Read only
상수 형태로 기술하는 문자열("Hello")이 저장된 메모리 영역이며, Text 영역처럼 읽기는 가능하나 쓰기는 허용되지 않는다.

Read/Write
정적 변수나 전역 변수들이 사용하는 메모리 영역이며, 별도로 초기화하지 않아도 0으로 초기화된다. 관리는 자동이라서 힙 영역 메모리처럼 할당 및 해제는 신경 쓸 필요는 없다.

메모리 영역에 관하여 더 자세한 내용은 PE image 혹은 PE Header라는 키워드로 검색해보면 됩니다. 여기서 PE는 Portable Executable의 약지로, 실행파일(확장자 .exe인 파일)의 형식을 의미합니다.

1.2 포인터 변수의 선언 및 정의

포인터 변수는 메모리의 주소를 저장하기 위한 전용 변수입니다. 메모리의 주소는 주소 연산을 사용하여 알 수 있습니다.
그리고 주소 연산과 정반대되는 개념의 연산자는 바로 간접지정 연산자* 입니다. 간접 지정 연산자는 자료형을 지정할 수 있습니다.
예제를 통해 살펴봅시다.

#include <stdio.h>

int main(void)
{
    // int 형식 변수 선언 및 정의
    int x = 10;
    // 변수 x를 기리키는 int 형식에 대한 포인트 변수 선언 및 정의
    int *pnData = &x;
    
    printf("x: %d\n", x); // => x: 10
    
    *pnData = 20;
    
    printf("x: %d\n", x); // => x: 20
    return 0;
}
int *pnData = &x;

위 식은 int 형에 대한 포인터의 선언 및 정의입니다. 식별자 pnData의 본질은 int형 변수가 아니라 포인터입니다. pnData에는 x의 메모리 주소가 할당됩니다. 따라서 위 식을 정리하면, 포인터 변수 pnData에 x의 메모리 주소를 할당하고 이 메모리 주소에 해당하는 값을 int형 변수로 취급하겠다는 뜻이 됩니다.

1.3 포인터와 배열

배열의 이름은 0번 요소의 주소이며, 전체 배열을 대표하는 식별자입니다. 포인터 변수는 주소를 저장하기 위한 변수입니다. 따라서, 배열의 이름이 주소이므로, 포인터 변수에 저장할 수 있습니다. int형 포인터에 int형 변수의 주소뿐만 아니라 int형 배열의 이름도 담을 수 있습니다.

#include <stdio.h>

int main(void)
{
    int aList[5] = { 0 };
    
    // aList는 배열 aList 0번 요소의 주소이므로 주소 연산자를 사용하지 않아도 된다.
    int *pnData = aList;
}
// *pnData = aList는 다음과 같다.
int *pnData = &aList[0]

또, pnData[0]은 다음과 같이 쓸 수 있다.

*(pnData + 0)

이 코드를 풀어서 설명하면, 포인터 변수 pnData에 저장된 주소를 기준으로 오른쪽으로 int 0개 떨어진 위치의 메모리를 int형 변수를 지정한다는 뜻입니다.

따라서, *(pnData + 1)은 pnData에 저장된 주소를 기준으로 int형 변수 1개 떨어진 위치, 그러니까 pnData로부터 4바이트 떨어진 위치의 메모리를 int형 변수로 해석한다는 뜻입니다.

1.4 메모리 동적 할당 및 관리

malloc()과 free() 함수는 메모리를 동적으로 할당하고 해제하는 함수입니다. 지금까지 변수를 선언하고 정의하는 방법은 자동으로 할당되고 해제되는 방법이었습니다. 반면, malloc()과 free() 함수는 우리가 메모리를 얼만큼 사용하고 언제 해제할 것인지 지정할 수 있습니다.

인자: size 할당받을 메모리의 바이트 단위 크기
반환값: 힙 영역에 할당된 메모리 중 첫 번째 바이트 메모리의 주소
void *malloc(size_t, size);
인자: memblock 반환할 메모리의 주소
void free(void *memblock);
반환값: 없음

malloc() 함수는 인수로 전달받은 정수만큼의 바이트 단위 메모리를 동적으로 할당하고 주소를 반한합니다. 그리고 사용이 끝난 다음에는 반드시 free() 함수를 이용해 메모리를 운영체제에 반환해야 합니다.

1.5 메모리 복사

보통, 단순 대입 연산자를 사용하면 r-value의 정보를 l-value에 복사합니다. 그러나 배열처럼 여러 인스턴스가 뭉쳐진 경우엔 단순 대입으로 r-value를 l-value로 복사할 수가 없습니다. 배열을 복사하려면 각 배열의 요소만큼 반복문을 수행하여 요소별로 일일이 단순 대입 연산을 수행해주어야 합니다.
이는 memcpy() 함수가 대신해줍니다.

인자:
 - dest: 대상 메모리 주소
 - src: 복사할 원본 데이터가 저장된 메모리 주소
 - count: 복사할 메모리의 바이트 단위 크기
void *memcpy(void *dest, const void *src, size_t count);
반환값: 대상 메모리 주소

⚠️ 오류 찾기 ⚠️

다음 코드에는 심각한 두 가지 문제가 있습니다. 두 가지 결함에 대해 생각해보고 바르게 고쳐봅시다.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
  char szBuffer[12] = { "HelloWorld" };
  char *pszData = NULL;
  
  pszData = (char*)malloc( sizeof(char) * 12 );
  pszData = szBuffer;
  puts(pszData);
  return 0;
}

💡해답
1. malloc() 함수를 사용하고 free()를 사용하여 메모리를 반환하지 않았다.
2. pszData = szBuffer 이 코드는 szBuffer 배열 요소 값들을 pszData에 할당하려는 의도였을 것이다. 그러나, 배열은 단순 대입 연산자로 모든 요소를 복사할 수 없다. 반복문을 사용하거나 memcpy() 함수를 사용해야 한다.

이 두 가지 실수를 동시에 해서 코드가 정상적으로 동작하는 것처럼 보입니다. 주의해야 합니다.

Reference

독하게 시작하는 c프로그래밍을 읽고 참조했습니다.

profile
프론트엔드 개발자가 되기 위해 공부 중입니다.

1개의 댓글

comment-user-thumbnail
2021년 5월 2일

c언어에 대해서 공부하시기 시작했나봐요. c언어만의 장점이 뭘까요?

답글 달기