이 게시글은 원본 링크
https://lsoovmee-rhino.tistory.com/27?category=847612
에서 공부를 하며 주석을 달고 오류가 있는 부분을 수정한 게시글임을 미리 알립니다.

C 프로그래밍 기초에서 배열과 포인터에 대해 공부하면서 배열에 대한 이해는 충분히 됐다고 생각합니다.

C 언어에서 배열과 포인터는 거의 동일한 역할을 합니다.

단지, 배열은 상수 포인터는 변수라는 차이점을 가지고 있을 뿐입니다.
///이는 int array = (int)malloc(sizeof(n)); 과 같이 동적할당을 통해 받은 메모리 주소를 배열로 활용할 때와
int array[];로 배열을 선언하여 활용할 때의 가장 큰 차이점이기도 하다.

상수는 등호의 왼쪽에 설 수 없습니다.

위의 예제를 보시면 이해가 가시죠?

3 = 2; 라고 선언할 수 없으니까요... (a는 &a[0]과 같고 &a[0]은 주소 값입니다.)

배열은 간단히 정의만 하고 넘어가겠습니다.

  1. 배열

배열은 동일한 타입의 데이터들을 묶는 구조입니다.

메모리의 연속된 위치에 차례대로 데이터를 저장합니다.

주소 당 1byte의 메모리가 할당됩니다.

즉, int a[3]는 각 원소 당 4byte의 메모리가 할당되고, 총 12byte의 메모리가 할당됩니다.

주소 0x00 ~ 0x03 // 0x04 ~ 0x07 // 0x08 ~ 0x0B

메모리 4byte // 4byte // 4byte

배열과 포인터는 다음과 같은 특징을 갖고 있습니다.

  1. 구조체

구조체는 하나 이상의 자료형을 기반으로 '사용자 정의 자료형'을 만들 수 있는 문법 요소입니다.

배열과 다른 점은 다양한 자료형을 포함하고 있다는 것입니다.

선언하는 방법이 다양한데 3가지로 나눠서 보겠습니다.

1)

struct A {

int a;

char b;

double c;

};

이렇게 구조체와 구조체 이름을 선언할 수 있습니다.

사용하기 위해선

('struct 구조체 이름 구조체 변수')

// A란 struct 이름이 있고, 거기에 구조체 변수를 정해준다. (B or C)

struct A B;

struct A C;

///구조체 변수란? 위에서 struct A라는 새로운 사용자 정의 자료형을 선언하였고 그 자료형의 변수 B,C를 구조체 변수라 한다.

B.a = 3;

B.b = 'c';

B.c = 0.2;

C.a = 1;

C.b = 'e';

C.c = 0.7;

2)

struct A {

int a;

char b;

double c;

} B;

B.a = 3;

B.b = 'c';

B.c = 0.2;

이렇게 구조체 이름 선언과 동시에 변수를 정해줄 수도 있습니다.

이것도 다시 struct A C; ('struct 구조체 이름 구조체 변수')와 같은 구문을 추가 함으로써 아래와 같은 코드를 작성할 수 있습니다.

C.a = 1;

C.b = 'e';

C.c = 0.7;

3)

typedef struct A {

int a;

char b;

double c;

} sA;

typedef @ &는 @를 앞으로 &라고 부를거야 라고 선언해주는 역할을 합니다.
///위에서는 struct A 를 SA로 부를거야 라고 하는 것

예를들어, typedef unsigned short int UINT16과 같이 unsigned short int를 typedef를 통해 앞으로 UINT16으로 쓸 수 있게 만듭니다.

위와 같이 구조체 이름을 선언한 경우 다음과 같이 구조체의 변수를 선언할 수 있습니다.

('struct 구조체 이름 구조체 변수' == '구조체 별명 구조체 변수') => 두가지 방법 모두 가능

sA B;

sA C;

B.a = 3;

B.b = 'c';

B.c = 0.2;

C.a = 1;

C.b = 'e';

C.c = 0.7;

가끔 이럴 때도 있습니다.

typedef struct {

int a;

char b;

double c;

} sA;

사용은 방금 전과 같습니다.

마지막 선언 방식은 익명 구조체라고 합니다.
/// 이 구조체의 이름은 없고 대신 선언은 struct (구조체 이름) 대신 sA로 '정의'한다.(type 'definition'라고 허는 것.
원래 '대신' 정의는 아닌데 여기서는 struct (구조체 이름) 을 구조체 이름이 없어서 못 하니......

물론 이름이 없기 때문에

'struct 구조체 이름 구조체 변수'와 같은 구문을 사용할 수 없습니다.

2-1. 구조체 포인터

다른 변수들과 마찬가지로 구조체도 포인터를 활용할 수 있습니다.

struct A {

int a;

char b[3];

};

위와 같이 선언되었을 때 구조체 이름을 다음과 같이 선언합니다.

struct A AA;

struct A *pA = &AA;

///복습! 메모라의 주소값을 저장하는 주소를 포인터라 한다.
정수인 변수가 저장된 메모리 주소를 가리키는 포인터를
int p와 같이 선언하듯이
구조체도
(사용자 정의 자료형)
pA와 같이 선언하여야 한다.

구조체 멤버는 다음과 같이 접근할 수 있습니다.

AA.a = 10;
AA.b[0] = "123";

//b[0] = '1'; b[1] = '2'; 이런 식으로 해주거나 strcpy를 써야한다.
AA.b = "123";도 안 된다.
배열의 초기화는 선언과 동시에 해줘야 한다.

(pA).a = 10; // AA.a = 10과 동일합니다. // 괄호가 중요합니다!!! 의 우선순위는 엄청 낮아요.

pA -> a = 10; // (*pA).a == AA.a = 10과 동일합니다.
///구조체 포인터 -> a = 10; 즉 그 구조체의 메모리 주소에 가서 그 구조체의 a변수의 값을 10으로 설정!
구조체 포인터를 알면 화살표를 통해 구조체 변수의 값을 바꿀 수 있다.

일반화!
구조체 포인터 -> 구조체 멤버 변수 = ~;
를 하면 해당 구조체의 멤버 변수의 값을 바꿀 수 있다.
(call by reference)

2-2. 구조체 배열

마찬가지로 구조체로 배열을 만들 수 있습니다.
///얘도 자료형이니까

struct A AA{3] = {{1, "123"}, {2, "234"}, {3, "345"}};
///int AA[3]과 큰 차이가 없다!

위와 같이 선언을 하면 AA[0], AA[1], AA[2] 3개의 구조체 변수가 생성된 것과 같습니다.
///int array[3]도 array[0],array[1],array[2]라는 변수 세개가 만들어 진 것과 같으니까!

초기화는 바로 괄호를 통해 해주었습니다.

단일 구조체 변수도 괄호로 초기화 할 수 있습니다.

(struct A BB = {4, "456"};)

2-3 구조체 포인터와 배열

구조체 배열을 구조체 포인터와 void 포인터를 통해 가리키는 것을 해보겠습니다.

일단 위와 동일한 struct A가 선언됐다고 가정하고 진행하겠습니다.

struct A AA{3] = {{1, "123"}, {2, "234"}, {3, "345"}};

struct A* p1;

(A는 int, char [3]로 구성되어 있으므로 (4+13)3 = 21의 메모리가 할당된다.)

(는 뻥입니다...하하하 8*3 = 24의 메모리가 할당됩니다.)

(글 말미에 설명드리겠습니다.)

void* p2 = AA; // p2에 AA 배열의 첫번째 요소 주소를 넣습니다.

이렇게 선언하고 난 다음에 다음과 같이 멤버 변수에 접근할 수 있습니다.

p1[0].a = 2; //== AA[0].a = 2;

p1[0].b[2] = '2'; //== AA[0].b[2] = '2';

///이유 p1은 AA의 첫칸 주소와 같으므로! 거기다 struct A* 이므로 배열의 한칸의 크기가 sizeof(struct A)임도 컴파일러에서 알 수 있다.

또는, 위와 같은 기능을 하기 위해

p1[0].a는 (*(p1+0)).a와 (p1+0) -> a로 바꿔 사용할 수 있습니다.
///각각 참조와 구조체 포인터

(원래 p.a를 (p).a로 쓸 수 있는데 (p).를 쓰기 귀찮아서 간접참조 연산자인 p->로 대체했다고 생각하시면 편합니다.)

(즉, p[].a == (*p).a == p->a가 모두 동일한 역할을 합니다.)

void 포인터 p2는 다음과 같이 멤버 변수에 접근할 수 있습니다.

(((struct A)p2+1)).a == ((struct A*)p2+1)->a
///p2[1]의 구조체 멤버 변수 a

(((struct A)p2+1)).b[1] == ((struct A*)p2+1)->b[1]
///p2[1]의 구조체 멤버 배열 b의 [1]

///형변환을 해서 이게 struct A의 포인터임을 알려줘야 컴파일러에서
배열의 1칸이 sizeof(struct A)임을 알지
(중요 !) array + 1 은 array[1]의 주소

p1과 거의 동일한데 강제 형변환 (struct A*)을 p2 앞에 붙여주는 것만 다릅니다.

아래 코드를 통해 결과를 확인하실 수 있습니다.

struct A{

int a;

char b[3];

};

struct A AA[3] = {{1, "123"}, {2, "234"}, {3, "345"}};

struct A* p1 = AA;

void* p2 = AA;

int main(int argc, const char argv[]) {
///const char
argv[]의 의미는 다음과 같다.
const는 상수 char* char포인터 argv는 배열
즉, char포인터 배열 argv를 만들고 이를 바꾸지 않겠다는 의미

그런데.. 왜.. 있는거지?

printf("%d\n", p1[1].a); //2

printf("%d\n", p1[1].b[1]); //3 



printf("%d\n", (p1+1)->a);  //2

printf("%d\n", (*(p1+1)).a);  //2



printf("%s\n", (p1+1)->b); // 234

printf("%s\n", (*(p1+1)).b); // 234



printf("%c\n", (p1+1)->b[1]); // 3

printf("%c\n", (*(p1+1)).b[1]); // 3
/// p1 + 1을 참조하면 구조체가 나오고 그 구조체의 b[1]을 프린트



printf("%c\n", *((p1+1)->b+1)); // 3

 

// printf("%c\n", ((p1+1).(b+1)); // 안 됨 ㅜㅜ

///안 되는 이유 : 참조를 해야하는데 (p1 + 1).(b + 1))은 메모리 주소가 아니기 떄문
그럼
((p1 + 1).(b + 1))은 ?
안됨 p1 + 1은 AA[1]의 메모리 주소이지 AA[1]은 아니다.
(((p1 + 1)).b+1)
b + 1 을 쓰려면 이렇게 써야한다. p1 + 1로 구조체를 참조하고 거기 있는 b배열의 주소를 불러와서 거기서 + 1
*(p1 + 1).(b + 1)은 안 된다. 왜냐면 b+1 은 변수 이름이 아니기 떄문

printf("%d\n", ((struct A*)p2)[1].a); // 2

printf("%c\n", ((struct A*)p2)[1].b[1]); //3

printf("%d\n", ((struct A*)p2+1)->a); //2

/// 화살표는 보면서 계속 익혀두자.
struct A포인터 p2 + 1에서 변수 a를 바로 찾아가는 것.

printf("%d\n", (*((struct A*)p2+1)).a); //2

printf("%s\n", ((struct A*)p2+1)->b); // 234
printf("%s\n", (*((struct A*)p2+1)).b); // 234

printf("%c\n", ((struct A*)p2+1)->b[1]); // 3
printf("%c\n", (*((struct A*)p2+1)).b[1]); // 3

printf("%c\n", *(((struct A*)p2+1)->b+1)); // 3



return 0;

}

  1. 자기 참조 구조체

구조체의 속성 중 하나가 스스로를 가리키는 구조체 입니다.

struct A{

char data;

struct A* A;

};

위와 같이 선언해 줄 수 있습니다.

연결 리스트의 구현에 많이 사용됩니다.

지금은 이름만 기억해주세요!!

큐, 스택 이후에 연결 리스트 게시물에서 주구장창 할 거에요...

**추가

구조체의 메모리 사이즈에 관하여!

구조체의 각 멤버 중에 가장 큰 멤버의 크기에 영향을 받는다!

struct A{

char a;

int b;

};

///8바이트

struct A{

char a;

char b;

int c;

};

여유가 생긴 a뒤 3byte 중에 b가 들어갔습니다.
///8바이트

struct A{

char a;

int c;

char b;

};

순서에도 영향을 받습니다. 크기가 작은 자료형을 앞에 몰아넣는 것이 유리할 것 같습니다.

///12바이트(a의 여유공간에 c가 4바이트이므로 못 들어감)

struct A{

char a;

double b;

};

2번 규칙이 적용되어 double 형의 크기를 따라갑니다.
///16바이트

struct A{

char a;

int c;

double b;

};

///16바이트

이 때 char 뒤에 여유공간을 두고 int가 들어감

따라서 char int char double로 선언하면 구조체의 사이즈는 24바이트가 됨

메모리 차지하는 원리

2의 n승만큼 칸을 나누어서 판단
ex.
가장 큰 자료형이 double일 때
char 다음 short면 바로 옆에 안 들어가고 한칸 띄워서
char 다음 char면 바로 다음 칸에
char 다음 int면 세칸 띄우고 들어감

참조 : https://blog.naver.com/sharonichoya/220495444611

다음 게시물에서는 큐와 스택을 들어가기 전 순서 리스트와 배열과 구조체를 통한 다항식의 표현에 대해 알아보겠습니다.

0개의 댓글