C에서 구조체와 field에 동적으로 메모리할 때의 과정은?

2013-03-19 22:35

C 과제를 하다가 의문나는 부분이 있어서 글 남겨 본다. 이 과제에 대한 내용과 풀이는 "character 데이터 복사 예제를 통한 memory allocation 개념 익히기" (http://www.slipp.net/wiki/pages/viewpage.action?pageId=9535493)) 글에서 볼 수 있다.

메모리를 할당할 때 구조체에 메모리를 할당하면 어떤 과정으로 진행될까?

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


struct string_pair {
    char *target;
    const char *source;
};


struct string_pair * alloc_str_pair(int target_size) {
    struct string_pair *str_pair;
    printf("str_pari address : %p \n", str_pair);


    str_pair = (struct string_pair *)malloc(sizeof(str_pair));
    printf("str_pari address : %p \n", str_pair);
    printf("str_pari size : %d \n", sizeof(str_pair));


    str_pair->target = (char *)malloc(target_size);
    printf("str_pari->target address : %p \n", str_pair->target);
    printf("str_pari->target size : %d \n", sizeof(str_pair->target));
    printf("str_pari->source address : %p \n", str_pair->source);


    return str_pair;
}

위와 같이 malloc function을 활용해 메모리를 할당하고 주소 값을 찍어봤다. 찍어본 결과 다음과 같은 결과를 얻을 수 있었다.

30;30;22mstr_pari address : 0x0  30;30;22mstr_pari address : 0x1455c0  30;30;22mstr_pari size : 4  30;30;22mstr_pari->target address : 0x145a60  30;30;22mstr_pari->target size : 4  30;30;22mstr_pari->source address : 0x0 

위와 같은 결과를 얻을 수 있었는데 이 개념을 자바에서 객체가 Heap 메모리에 객체를 생성하고 이에 대한 reference를 가지고 객체 안에 있는 String field가 Heap 메모리에 String 객체를 생성하고 이에 대한 reference를 가지는 것으로 이해하면 될까? 그럼 C에서 malloc을 사용하면 동적으로 메모리를 할당하는데 이 부분의 크기도 자바의 Heap 메모리와 같이 가변적인 크기의 데이터를 할당할 수 있는 것으로 이해하면 될까? malloc을 자바의 Heap 메모리에 대한 reference로 이해하려고 하니 대략적인 이해는 되지만 명확하지 않은 부분이 있어 질문으로 남겨본다.

1개의 의견 from FB

6개의 의견 from SLiPP

2013-03-19 22:56

그냥 자바는 잊어버리시는게 좋을 것 같네요 :) C는 시키는 것 외에는 (자바에 비해서 거의) 아무것도 하지 않습니다.

위에서 생각해야할 자바랑 C랑 다른 점은 C 런타임은 할당하는 메모리 내용에 대해서 모른다는 것입니다. 어떤 구조의 객체를 생성하는 것이 아니라 그냥 메모리를 할당하는 것이죠. 그래서 malloc의 파라메터가 구조체 이름이 아니라 할당할 메모리 크기입니다.

위의 코드에서 sizeof를 쓰셨는데 항상 메모리 할당을 한 후에 쓰셨네요. 어떤 의도가 있었는지는 모르겠지만 sizeof는 실제 할당된 메모리가 아니라 컴파일 당시에 결정되는 자료형의 크기를 돌려주는 연산자입니다. 즉 할당하기 전이나 후나 같은 값을 돌려줍니다. 포인터는 단지 메모리 주소일 뿐이고 그 외의 아무런 정보도 없습니다.

2013-03-19 23:20

@eungju.park.1 위의 코드에서 sizeof를 쓰셨는데 항상 메모리 할당을 한 후에 쓰셨네요. 어떤 의도가 있었는지는 모르겠지만 sizeof는 실제 할당된 메모리가 아니라 컴파일 당시에 결정되는 자료형의 크기를 돌려주는 연산자입니다. => 위 내용이 잘 이해가 되지 않는다. 메모리를 할당했다는 것이 struct string_pair *str_pair;와 같이 선언하는 순간에 이미 메모리가 할당이 되었다는 것인가? 만약 그렇다면 위 출력 결과 보면 알겠지만 메모리 주소가 0으로 나오고 있는데 메모리가 할당된 것이 맞는 건가? 위 코드는 내가 의도한 부분은 없고 아직 C를 잘 모르는 상태에서 구현 방법을 찾다보니 위와 같이 구현했다. 위 코드를 다시 구현한다면 어떻게 구현하는 것이 좋을까?

포인터에 대한 대략적인 개념은 알겠는데 직접 구현하면서 메모리가 할당되고 해제되는 구체적인 부분이 잘 이해가 되지 않네. 역시 메모리 관리를 직접하는게 아직 익숙하지 않네.

2013-03-19 23:46

struct string_pair *str_pair;

함수내에서 위와 같이 선언하면 str_pair는 string_pair 구조체에 대한 포인터이기 때문에 포인터 크기 만큼만 스택에 잡혀요. sizeof(str_pair)도 str_pair이 포인터이기 때문에 포인터의 크기인 4가 됩니다. 아직 구조체의 크기 만큼 메모리는 할당되지 않았습니다.

printf("str_pari address : %p \n", str_pair);

str_pair에 담긴 값을 출력합니다. str_pair을 초기화하지 않았으니 0이 아닌 쓰레기값이 나올 수도 있습니다. 포인터는 단순히 메모리 주소이기 때문에 이것만 보고는 알 수 있는 정보가 없습니다.

str_pair = (struct string_pair *)malloc(sizeof(str_pair));

이 때 4바이트만 힙에 할당됩니다. 아마 원래 의도는 sizeof(struct string_pair)였을 것 같네요. 이렇게 해야 포인터의 크기가 아닌 구조체의 크기 8 바이트 메모리가 할당됩니다. 그리고 malloc는 할당된 메모리의 시작 주소를 돌려줍니다. 그래서 str_pair은 할당된 메모리의 시작 주소를 가리킵니다.

printf("str_pari address : %p \n", str_pair);

할당된 메모리의 시작 주소가 찍히겠죠.

printf("str_pari size : %d \n", sizeof(str_pair));

여기서 4가 찍힙니다. str_ptr에 어떤 주소가 담겨 있던 포인터의 크기니까 항상 4입니다.

C는 시키는 일 말고는 안한다. 요점을 항상 떠올리시면 도움이 될 것 같네요.

2013-03-20 00:01

시키는 것 외에는 아무것도 안하다고 하면 자바도 마찬가지니까 ㅎㅎㅎ 어느 정도 수준인지 몇가지 예를 들겠습니다.

  1. 자바에서는 변수 선언하면 정해진 기본 값으로 초기화가 됩니다. int는 0, boolean은 false, Object는 null. 하지만 C는 스택(함수 내에서는)에 공간만 확보하고 초기화하지 않습니다. int도 쓰레기값, 포인터도 쓰레기값이 들어있습니다.

  2. 힙 메모리를 할당해도 초기화되어 있지 않습니다. malloc(8)하면 8만큼의 공간만 힙메모리에서 확보합니다. 0으로 초기화까지 하는 함수는 calloc로 따로 있습니다.
  3. 실행하는 동안에는 프로그래머가 따로 관리하지 않으면 할당된 메모리에 대한 정보를 전혀 관리하지 않습니다. 할당된 영역의 크기가 얼마인지, 내용의 타입은 무엇인지 전혀 모릅니다.
  4. NullPointException 같은 것 없습니다. 그냥 잘 돌아가거나, 이상 동작을 하거나 어떻게 될지 모릅니다.
  5. 배열 등에서 인덱스 범위 검사하지 않습니다. 그냥 시키는 대로 값을 읽거나 씁니다.

2013-03-20 00:35

@eungju.park.1 str_pair = (struct string_pair *)malloc(sizeof(str_pair));

이 부분에서 내가 하고 싶은 것이 8바이트만큼의 공간이 확보되리라 생각했는데 위 printf 출력 결과가 4인 것을 보고 의아했는데 네 답변 보니까 4가 찍히는 것이 맞겠네. 포인터 주소니까. 그래서 네 답변과 페북 답변보고 변경해 보니 8로 잘 찍히네.

str_pair = (struct string_pair *)malloc(sizeof(struct string_pair)); 또는 str_pair = (struct string_pair *)malloc(sizeof(*str_pair));

이 부분은 처음에 C하는 사람들은 많이 헷갈릴 듯하다. 메모리 동적으로 할당하는 부분도 헷갈리는데 메모리 크기 구하는 것도 쉽지 않네. 오늘도 너 때문에 하나 배우고 간다. 고맙다.

"C는 시키는 일 말고는 안한다. 요점을 항상 떠올리시면 도움이 될 것 같네요." => 이 조언 항상 잊지 않으마.

2013-03-20 00:53

@자바지기

음 즉 sizeof(str_pair) 라고 하면 sizeof(struct string_pair *)이 됩니다. 배열을 동적으로 할당할 때에도 마찬가지인데요, x86 기준으로 int a[10]; 위의 경우 sizeof(a) == sizeof(int) * 10 == 40 이 되고 int * a = (int *)malloc(sizeof(int) * 10); 위의 경우 sizeof(a) == sizeof(int *) == 4 가 됩니다. 또, sizeof(*a) == sizeof(int) == 4 가 됩니다.

str_pair = (struct string_pair *)malloc(sizeof(*str_pair)); 위와 같은 식의 할당은 할당되어 있지 않은 str_pair 라는 변수를 개념상으로 참조하는 식이기 때문에, 실행에 문제가 없더라도 좋은 표현식은 아닌 것 같습니다.

malloc 의 경우 말씀하신 대로 Heap 영역에 크기만큼 메모리가 가변적으로 잡힙니다. 자바의 타입 대부분은 reference 타입인데, C에서는 기본적으로 타입이 value이고 포인터가 붙은 것이 자바의 타입에 대응한다고 생각하시면 될 것 같습니다.

str_pair 를 str_pair->target 보다 먼저 동적할당해야 하는 이유는 str_pair 가 할당되지 않은 상태에서는 target 에 접근할 수 없기 때문입니다. (str_pair 의 형이 string_pair * 가 아니라 string_pair 형이라면 선언시 스택에 할당이 되어 가능합니다.) target은 할당된 위치에 상주하게 되죠.

해제 역시 str_pair가 먼저 해제되면 str_pair->target 에는 접근할 수 없기 때문에 target 을 먼저 해제해 주어야 합니다. (해제는 할당의 역순)

그런데 사실, 링크의 소스에서는 str_pair 를 동적으로 할당할 이유는 없었습니다. (물론 예제 면에서는 굉장히 좋았다고 생각합니다 :) 실제로 동적할당 되어서 변경되어야 하는 부분은 target 이기 때문에 리턴값을 string_pair * 가 아닌 string_pair 로 한 뒤 local variable 자체를 리턴하더라도 괜찮습니다. 단, return &str_pair 처럼 레퍼런스를 리턴하면 문제가 발생합니다.

의견 추가하기

연관태그

← 목록으로