본문 바로가기
c언어

c언어 구조체에서 사용되는 포인터에 대하여 알아보기

by 개발자 L 2023. 1. 29.
반응형

c언어 구조체에서 사용되는 포인터에 대하여 알아보기

네 안녕하세요 이번 포스팅에서는 구조체에서 사용되는 포인터에 대하여 알아보려고 합니다.

우리가 구조체를 사용할 때 사용이 되는 포인터는 크게 두 가지입니다.

  • 구조체를 가리키는 포인터
  • 포인터를 멤버로 가지는 구조체

이렇게 두 가지인데,

이 둘을 차례대로 살펴보면서 어떻게 사용이 되는지 한 번 알아보도록 하겠습니다.

 

1. 구조체를 가리키는 포인터

구조체를 가리키는 포인터는 말 그대로 구조체를 가리키고 있는 포인터입니다.

우리가 변수를 포인터로 가리키는 것과 똑같습니다.

사용 방법은 이렇습니다.

struct student s = {24, "Kim", 3.5};
struct student *p;

p = &s;

printf("학번 = %d, 이름 = %s, 학점 = %lf\n", (*p).number, (*p).name, (*p).grade);

이렇게 사용을 합니다.

여기서 포인터는 어떤 데이터의 주소값을 나타내기 때문에 항상 포인터 선언을 하면 그 포인터가 가리켜야 하는 주소를

선언을 해줘야 하는 것을 잊으면 안 됩니다.

그리고 출력을 할 때도 구조체의 주소를 받은 것이기 때문에, 해당 구조체 안에 있는 멤버를 주소를 통해 간접 참조를

할 수 있도록 '(*p). 멤버 변수명'과 같은 형식으로 형식 지정자들과 대응이 되는 변수들을 지정을 해줘야 합니다.

 

1 - 1. 포인터를 이용하여 구조체의 멤버 가리키기

포인터를 이용해서 구조체의 멤버를 가리키는 것은 프로그래밍을 할 때 정말 자주 씁니다.

포인터 자체가 활용도가 높은 만큼, 구조체를 다룰 때에도 많이 쓰게 됩니다.

그래서 이를 위한 연산자도 존재를 하는데, 화살표 표시(->)를 합니다.

그리고 이를 '간접 멤버 연산자'라고 합니다.

이를 이용해 구조체 멤버에 접근을 합니다.

사용 방법은 이러합니다.

p -> number;

이렇게 쓰면 구조체 안에 있는 멤버 변수인 number에 접근을 하겠다는 뜻이 됩니다.

 

1 - 2. 혼동하기 쉬운 구조체를 가리키는 포인터

구조체를 가리키는 포인터를 쓸 때 생각보다 혼동이 되어 오류를 내는 경우가 정말 많습니다.

그래서 제가 간단히 정리를 해봤습니다.

  • (*p). number : 포인터 p가 가리키는 구조체 멤버 number
  • p -> number : 포인터 p가 가리키는 구조체 멤버 number((*p). number과 동일하다.)
  • *p.number : 구조체 p의 멤버 number가 가리키는 것, 이때 number은 반드시 포인터여야만 에러가 나지 않으며, 연산자 우선순위 규칙에 따라서 *(p.number)과 의미가 같음.
  • *p -> number : p가 가리키는 멤버 number가 가리키는 것, 이 때 number은 반드시 포인터여야만 하며, 연산자 우선 순위 규칙에 따라서 *(p -> number)가 되며, *p.number과 의미가 같음.

이 부분들에 대하여 혼동을 하지 않고 잘 쓰셔서 오류 없이 해결을 하실 수 있으면 좋겠습니다:)

반응형

 

2. 포인터를 멤버로 가지는 구조체

구조체의 특징 중 하나가 포인터를 멤버로 가질 수 있다는 것입니다.

그 이유는 어떤 자료형이든 구조체는 하나의 멤버로 묶을 수 있기 때문입니다.

포인터로 지정이 가능한 것들은 우리가 사용하는 여러 가지 자료형들,

그리고 다른 구조체에 대한 포인터 역시 가능합니다.

그럼 이를 가지고 한 번 실습을 해보도록 하겠습니다.

#include <stdio.h>

struct date
{
    int month;
    int day;
    int year;
};

struct student
{
    int number;
    char name[50];
    double grade;
    struct date *dob; // date of birth
};

int main()
{
    struct date d = {3, 20, 1989};
    struct student s = {20230001, "Lee", 3.6};

    s.dob = &d;

    printf("학번 : %d\n", s.number);
    printf("이름 : %s\n", s.name);
    printf("학점 : %lf\n", s.grade);
    printf("생년월일 : %d년 %d월 %d일\n", s.dob -> year, s.dob -> month, s.dob -> day);

    return 0;
}

이렇게 한 번 작성을 해봤고, 구조체를 멤버로 가지는 구조체 내에 있는 구조체를 포인터로 접근해서

그 정보를 출력을 해봤습니다.

그럼 결과를 한 번 보도록 하겠습니다.

학번 : 20230001
이름 : Lee
학점 : 3.600000
생년월일 : 1989년 3월 20일

잘 나온 것을 확인할 수 있습니다.

 

3. 문자 배열과 문자 포인터의 차이점

그리고 여기서 중요한 부분이 있습니다.

우리가 구조체를 선언할 때 문자열을 써야 하는 경우도 생길 때가 많을 것입니다.

이 경우에 우리는 선택지가 2가지가 있겠죠?

  • 특정 문자열을 위해서 문자 배열을 생성한다.
  • 문자형을 가리키는 포인터를 생성하고 여기에 문자열 상수를 대입한다.

일단 결론부터 말씀을 드리면 둘 다 문제가 없습니다.

하지만 둘은 꽤 큰 차이를 가지고 있기 때문에 잘 숙지를 하고 있어야 나중에 실제 작업 시에 문제가 없을 겁니다.

일단 각각 선언하는 형태를 먼저 봅시다.

// 문자 배열을 생성하는 경우
struct studentA
{
    int number;
    char name[50];
    double grade;
};

// 문자 포인터를 생성하는 경우
struct studentB
{
    int number;
    char *p;
    double grade;
};

struct studentA s1 = {20230001, "홍길동", 3.2};
struct studentB s2 = {20230002, "강감찬", 2.4};

이렇게 쓸 수 있습니다.

여기까지는 크게 문제가 없습니다.

하지만, 이제 저장이 되는 방법과 위치에서 차이가 날 겁니다.

구조체 변수 s1에 있는 이름은 문자 배열 안에 저장이 되고, 문자 배열의 할당량은 50바이트입니다.

그리고 우리가 지금껏 잘 써왔던 아래와 같은 방법으로 저장을 할 수 있습니다.

struct studentA s1;
scanf("%s", s1.name);

하지만 s2의 경우는 포인터만 생성을 해뒀기 때문에 포인터만을 위한 공간인 4바이트만 할당이 되어있습니다.

다시 말하면 문자열을 저장할 수 있는 공간은 존재하지 않는다는 것입니다.

그렇기 때문에 문자열이 어딘가 이미 선언이 되어 저장이 되어 있는 경우 그 주소값을 가져와서

간접 참조만이 가능하다는 것입니다.

그래서 이런 경우에는 이런 식으로 써야 합니다.

struct studentB s2;
s2.p = "강감찬";

이런 식으로 문자열 상수를 지정해서 받아줘야 합니다.

 

여기까지 구조체에서 사용되는 포인터에 대하여 알아보았는데요,

다음 포스팅에서는 구조체와 함수에 대하여 알아보도록 하겠습니다.

긴 글 읽어주신 독자분들께 진심으로 감사드립니다~

반응형

댓글