Skip to content

42. 구조체 포인터


1. 키워드

  • 구조체 포인터


2. 구조체 포인터 사용하기

  • 보통 구조체는 멤버 변수가 여러 개 들어있어서 크기가 큰 편이다.
  • 그래서 구조체 변수를 일일이 선언해서 사용하는 것보다는 포인터에 메모리를 할당해서 사용하는 편이 효율적이다.


3. 구조체 포인터를 선언하고 메모리 할당하기

  • 다른 자료형과 마찬가지로 구조체도 포인터를 선언할 수 있으며, 구조체 포인터에는 malloc 함수를 사용하여 동적 메모리를 할당할 수 있다.


struct 구조체이름 *포인터이름 = malloc(sizeof(struct 구조체이름));
#define _CRT_SECURE_NO_WARNINGS // strcpy 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>
#include <string.h> // strcpy 함수가 선언된 헤더 파일
#include <stdlib.h> // malloc, free 함수가 선언된 헤더 파일

struct Person // 구조체 정의
{
    char name[20];     // 구조체 멤버 1
    int age;           // 구조체 멤버 2
    char address[100]; // 구조체 멤버 3
};

int main()
{
    struct Person *p1 = malloc(sizeof(struct Person)); // 구조체 포인터 선언, 메모리 할당

    // 화살표 연산자로 구조체 멤버에 접근하여 값 할당
    strcpy(p1->name, "홍길동");
    p1->age = 30;
    strcpy(p1->address, "서울시 용산구 한남동");

    // 화살표 연산자로 구조체 멤버에 접근하여 값 출력
    printf("이름: %s\n", p1->name);
    printf("나이: %d\n", p1->age);
    printf("주소: %s\n", p1->address);

    free(p1); // 동적 메모리 해제

    return 0;
}

// 이름: 홍길동
// 나이: 30
// 주소: 서울시 용산구 한남동


  • 먼저 struct Person *p1과 같이 struct 키워드와 구조체 이름을 사용하여 구조체 포인터를 선언한다.
  • 이때 일반 변수가 아닌 포인터 변수이므로 반드시 *를 붙인다.
  • 그리고 malloc 함수로 메모리를 할당할 때 크기를 알아야 하므로 sizeof(struct Person)과 같이 구조체 크기를 구해 넣어준다.


struct Person *p1 = malloc(struct Person)); // 구조체 포인터 선언, 메모리 할당


  • 지금까지 .을 사용해서 멤버에 접근했지만, 구조체 포인터의 멤버에 접근할 때는 ->(화살표 연산자)를 사용한다.


// 화살표 연산자로 구조체 멤버에 접근하여 값 할당
strcpy(p1->name, "홍길동");
p1->age = 30;
strcpy(p1->address, "서울시 용산구 한남동");

// 화살표 연산자로 구조체 멤버에 접근하여 값 출력
printf("이름: %s\n", p1->name);
printf("나이: %d\n", p1->age);
printf("주소: %s\n", p1->address);


  • p1->age = 30;과 같이 구조체 포인터의 멤버에 접근한 뒤 값을 할당하고, p1->age와 같이 값을 가져온다.
  • p1->name 등의 문자열 멤버는 =로 저장할 수 없으므로 strcpy 함수를 사용하면 된다.
  • 마지막으로 free(p1);처럼 할당한 메모리를 해제해 준다.


구조체 포인터에서 .으로 멤버에 접근하기

  • 구조체 포인터에서 멤버에 접근하려면 p1->age와 같이 화살표 연산자를 사용하는데 ()(괄호)와 *(역참조)를 사용하면 .으로 멤버에 접근할 수 있다.


p1->age;   // 화살표 연산자로 멤버에 접근
(*p1).age; // 구조체 포인터를 역참조한 뒤 .으로 멤버에 접근


  • (*p1).age와 같이 구조체 포인터를 역참조하면 pointer to struct Person에서 pointer to가 제거되어서 struct Person이 된다.
  • 따라서 .으로 멤버에 접근할 수 있다.


구조체의 멤버가 포인터일 때 역참조하기

  • 구조체의 멤버가 포인터일 때 역참조를 하려면 맨 앞에 *를 붙이면 된다.
  • 이때 구조체 변수 앞에 *가 붙어있더라도 멤버의 역참조이지 구조체 변수의 역참조가 아니다.


*구조체변수.멤버
*구조체포인터->멤버
#include <stdio.h>
#include <stdlib.h>

struct Data
{
    char c1;
    int *numPtr; // 포인터
};

int main()
{
    int num1 = 10;
    struct Data d1;                                // 구조체 변수
    struct Data *d2 = malloc(sizeof(struct Data)); // 구조체 포인터에 메모리 할당

    d1.numPtr = &num1;
    d2->numPtr = &num1;

    printf("%d\n", *d1.numPtr);  // 구조체의 멤버를 역참조
    printf("%d\n", *d2->numPtr); // 구조체 포인터의 멤버를 역참조

    d2->c1 = 'a';

    printf("%c\n", (*d2).c1);      // 구조체 포인터를 역참조하여 c1에 접근
                                   // d2->c1과 같음
    printf("%d\n", *(*d2).numPtr); // 구조체 포인터를 역참조하여 numPtr에 접근한 뒤 다시 역참조
                                   // *d2->numPtr과 같음

    free(d2);

    return 0;
}

// 10
// 10
// a
// 10


  • 구조체 변수 d1의 멤버 numPtr을 역참조하는 방법과 구조체 포인터 d2의 멤버 numPtr을 역참조하는 방법을 그림으로 표현하면 다음과 같은 모양이 된다.


001


  • 만약 역참조한 것을 ()로 묶으면 구조체 변수를 역참조한 뒤 멤버에 접근한다는 뜻이 된다.
  • *(*d2).numPtr처럼 구조체 포인터를 역참조하여 numPtr에 접근한 뒤 다시 역참조할 수도 있다.


(*구조체포인터).멤버
*(*구조체포인터).멤버


  • 여기서 (*d2).c1d2->c1과 같고, *(*d2).numPtr*d2->numPtr과 같다.
  • 즉, 구조체 포인터를 역참조한 뒤 ()로 묶으면 -> 연산자에서 . 연산자를 사용하게 되므로 포인터가 일반 변수로 바뀐다는 뜻이다.


002


4. 구조체 별칭으로 포인터를 선언하고 메모리 할당하기

  • struct 키워드로 포인터를 선언하고 메모리를 할당했으니, typedef로 정의한 구조체 별칭으로도 포인터를 선언하고 메모리를 할당할 수 있다.


구조체별칭 *포인터이름 = malloc(sizeof(구조체별칭));
#define _CRT_SECURE_NO_WARNINGS // strcpy 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>
#include <string.h> // strcpy 함수가 선언된 헤더 파일
#include <stdlib.h> // malloc, free 함수가 선언된 헤더 파일

typedef struct _Person // 구조체 이름은 _Person
{
    char name[20];     // 구조체 멤버 1
    int age;           // 구조체 멤버 2
    char address[100]; // 구조체 멤버 3
} Person;              // typedef를 사용하여 구조체 별칭을 Person으로 정의

int main()
{
    Person *p1 = malloc(sizeof(Person)); // 구조체 별칭으로 포인터 선언, 메모리 할당

    // 화살표 연산자로 구조체 멤버에 접근하여 값 할당
    strcpy(p1->name, "홍길동");
    p1->age = 30;
    strcpy(p1->address, "서울시 용산구 한남동");

    // 화살표 연산자로 구조체 멤버에 접근하여 값 출력
    printf("이름: %s\n", p1->name);
    printf("나이: %d\n", p1->age);
    printf("주소: %s\n", p1->address);

    free(p1);

    return 0;
}

// 이름: 홍길동
// 나이: 30
// 주소: 서울시 용산구 한남동


  • 구조체 별칭을 사용하면 포인터를 선언하고 메모리를 할당하는 방법이 좀 더 간단하다.
  • Person *p1과 같이 구조체 별칭으로 포인터를 바로 선언한 뒤 malloc 함수로 메모리를 할당한다.
  • 이때 할당할 메모리 크기도 sizeof(Person)처럼 구조체 별칭으로 구하면 된다.


Person *p1 = malloc(sizeof(Person)); // 구조체 별칭으로 포인터 선언, 메모리 할당


  • 구조체 별칭으로 선언한 포인터도 구조체 멤버에 접근할 때는 ->를 사용한다.


// 화살표 연산자로 구조체 멤버에 접근하여 값 할당
strcpy(p1->name, "홍길동");
p1->age = 30;
strcpy(p1->address, "서울시 용산구 한남동");

// 화살표 연산자로 구조체 멤버에 접근하여 값 출력
printf("이름: %s\n", p1->name);
printf("나이: %d\n", p1->age);
printf("주소: %s\n", p1->address);


  • 마지막으로 구조체를 다 사용했다면 반드시 free 함수로 메모리를 해제해 준다.


  • 익명 구조체도 실제로 사용하려면 구조체 별칭을 지정해 줘야 하므로 메모리 할당 방법은 앞과 같다.


#define _CRT_SECURE_NO_WARNINGS // strcpy 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>
#include <string.h> // strcpy 함수가 선언된 헤더 파일
#include <stdlib.h> // malloc, free 함수가 선언된 헤더 파일

typedef struct // 구조체 이름이 없는 익명 구조체
{
    char name[20];     // 구조체 멤버 1
    int age;           // 구조체 멤버 2
    char address[100]; // 구조체 멤버 3
} Person;              // typedef를 사용하여 구조체 별칭을 Person으로 정의

int main()
{
    Person *p1 = malloc(sizeof(Person)); // 구조체 별칭으로 포인터 선언, 메모리 할당

    // 화살표 연산자로 구조체 멤버에 접근하여 값 할당
    strcpy(p1->name, "홍길동");
    p1->age = 30;
    strcpy(p1->address, "서울시 용산구 한남동");

    // 화살표 연산자로 구조체 멤버에 접근하여 값 출력
    printf("이름: %s\n", p1->name);
    printf("나이: %d\n", p1->age);
    printf("주소: %s\n", p1->address);

    free(p1);

    return 0;
}

// 이름: 홍길동
// 나이: 30
// 주소: 서울시 용산구 한남동


5. 구조체 포인터에 구조체 변수의 주소 할당하기

  • 지금까지 malloc 함수로 구조체 포인터에 동적 메모리를 할당했다.
  • 동적 메모리를 할당하지 않고 구조체 포인터를 사용하려면 구조체 변수에 &(주소 연산자)를 사용하면 된다.


구조체포인터 = &구조체변수;
#include <stdio.h>

struct Person // 구조체 정의
{
    char name[20];     // 구조체 멤버 1
    int age;           // 구조체 멤버 2
    char address[100]; // 구조체 멤버 3
};

int main()
{
    struct Person p1;   // 구조체 변수 선언
    struct Person *ptr; // 구조체 포인터 선언

    ptr = &p1; // p1의 메모리 주소를 구하여 ptr에 할당

    // 화살표 연산자로 구조체 멤버에 접근하여 값 할당
    ptr->age = 30;

    printf("나이: %d\n", p1.age);   // 구조체 변수의 멤버 값 출력
    printf("나이: %d\n", ptr->age); // 구조체 포인터의 멤버 값 출력

    return 0;
}

// 나이: 30
// 나이: 30


  • 먼저 struct Person p1;과 같이 구조체 변수를 선언하고, struct Person *ptr;과 같이 구조체 포인터를 선언했다.
  • 아직까지는 p1ptr은 관계가 없다.


  • 구조체 변수는 &를 사용하여 메모리 주소를 구할 수 있으며, 이렇게 구한 메모리 주소는 구조체 포인터에 할당할 수 있다.


ptr = &p1; // p1의 메모리 주소를 구하여 ptr에 할당


  • ptr은 구조체 포인터이므로 ->로 멤버에 접근하여 값을 할당했다.
  • 그리고 printf 함수로 p1의 멤버와 ptr의 멤버를 출력해 보면 같은 값이 나오는 것을 알 수 있다.


// 화살표 연산자로 구조체 멤버에 접근하여 값 할당
ptr->age = 30;

printf("나이: %d\n", p1.age);   // 구조체 변수의 멤버 값 출력
printf("나이: %d\n", ptr->age); // 구조체 포인터의 멤버 값 출력


  • 즉, ptrp1의 메모리 주소를 할당했으므로 ptr의 멤버를 수정하면 결국 p1의 멤버도 바뀐다.
  • 접근하는 방식만 차이가 있을 뿐 결국 같은 곳의 내용을 수정하게 된다.


003


References