일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- C언어
- MFC
- 김성엽
- MFC 예제
- mfc 실습
- 정보처리기사
- visual
- C++
- 도움말
- C
- tips강좌
- linux
- Tipsware
- 마이크로소프트
- 별찍기
- MyTread
- 포트폴리오
- win32
- 핵심 요약
- 충무창업큐브
- MyThread
- 실습
- 정처기 독학
- ListBox
- 미가공
- Time
- SSG
- do it c
- 유료강좌
- mysql
- Today
- Total
History
c 언어 온라인 무료강좌 7차시 정리 본문
해당 게시물은 김성엽 선생님의 강의를 바탕으로 만든 게시물입니다.
● 이차 포인터
먼저 상식적인 일차 포인터를 구현해보자
int main()
{
int data;
int *p;
p=&data;
*p=5; //data=5;
}
이런 방법으로 포인터를 사용할 수 있다.
그럼 일반 변수에다가 주소값을 대입하는 방법을 보자. 매우 간단하다.
int main()
{
int data;
int *p;
int temp;
temp=&data; //가능하긴 하나 경고 발생
}
이렇게 temp라는 일반 변수도 주소값을 대입할 수 있다. 그러나 이럴경우 경고가 발생하는데 그 이유는
일반 변수인데 주소를 넣으려고 해서 그렇다.
그렇다면 경고를 없앨 수 있는 방법이 뭐가 있을까? 그것은 형변환을 해주는 것이다.
int main()
{
int data;
int *p;
int temp;
temp=(int)&data; //int 형으로 형변환을 해준다.
}
위의 코드처럼 int형으로 형변환을 해주면 temp에 주소값을 넣는 것이 단순히 저장개념으로 사용한다는 뜻을 가진다.
왜냐하면 temp는 일반 변수이기 때문에 *temp=5;라는 코드를 사용하지 못한다.
(일반 변수이기 때문에 번지 지정 포인터를 사용 못하기 때문)
그러면 왜 이런짓을 하는걸까?
아래 코드를 보자.
int main()
{
int data;
int *p;
int temp;
temp=(int)&data; //int 형으로 형변환을 해준다.
p=temp; //temp가 가지고 있는 값은 주소값이다. 그래서 이런 코드가 가능하다.
}
아래 코드를 보면 p=temp라는 코드가 있는데 이 코드는 가능하다. 왜냐하면 temp는 자신이 *연산을 하는 것이 아니라 단순 대입 개념으로 사용했기 때문에 가능한 것이다. 그러나 이러한 코드도 경고가 뜰 것이다. 그러면 어떻게 해야 경고가 뜨지 않고 코드를 짤 수 있을까?
아래 코드를 보자.
int main()
{
int data;
int *p;
int temp;
temp=(int)&data;
p=(int *)temp;
*p=6; //data=6;
}
그렇다 아까 했던 것처럼 경고가 안 뜨게하려면 포인터 형식으로 형변환 해주면 해결된다. p는 포인터 변수이기 때문에 (int*)으로 일반변수를 형변환 해준것이다.
그래서 위처럼 temp를 대입해도 *p를 이용하면 data에 영향을 줄 수 있는 주소값을 얻을 수 있다.
이해가 잘 안 갈 수도 있으니 그림으로 보자.
결론: 주소 저장만이 목적이라면 굳이 포인터 변수를 사용하지 않더라도 일반 4byte 변수를 선언하여 저장이 가능
but 직접사용은 불가능 또한 코드가 복잡해짐
-data의 주소값은 100이라고 가정
-포인터 변수 p 선언
-일반 변수 temp 선언
1: 형변환으로 인해 temp에 data의 주소값을 대입(상수 값으로 대입)
2: temp를 *temp로 참조할 수 없다. 왜냐하면 temp는 일반변수이기 때문에
3: 포인터 변수 p에 temp에 저장되어있는 data의 주소값 100을 형변환하여 대입
4: *p로 data의 주소값을 참조하여 값 변경
근데 데이터의 흐름이 아래 그림처럼 temp가 data를 참조할 수 있으면 더 효율적이지 않을까?
그럼 아래 그림처럼 하려면 어떻게 해야할까?
temp의 값을 *p로 옮기는 것이 아니라 temp의 주소값을 *p로 옮기면 어떻게 될 것같다. 한번 해보자
int main()
{
int data;
int *p;
int temp;
temp=(int)&data;
p=&temp;
*p=6; //temp=6; ?????
}
내가 원하는대로 바꿔봤더니 여전히 temp에서 data를 참조하지 못한다. 때문에 temp의 값이 바뀐다.
이건 내가 원하는 상황이 아니다.
그럼 한 번 더 수정해보자.
int main()
{
int data;
int *p, *p_temp;
int temp;
temp=(int)&data;
p=&temp;
p_temp=(int*)*p; //*p의 값은 temp의 주소값 즉 100이다. *p_temp는 포인터 변수이다
//*p를 해주는 순간 자료형이 int로 바뀌기 때문에 (int *)를 붙혀서
//형 변환을 해준다.
*p_temp=6; //data=6;
}
이번에는 *p_temp라는 포인터 변수를 하나 더 추가해봤다. 이렇게 하면 정상적으로 *p_temp를 이용해서 data의 값을 참조할 수 있다.
이 코드의 흐름을 그림으로 알아보자.
-data의 주소값은 100이라고 가정
-포인터 변수 p 선언
-포인터 변수 p_temp 선언
-일반 변수 temp 선언
1: 형변환으로 인해 temp에 data의 주소값을 대입(상수 값으로 대입)
2: *p에 temp의 주소값을 대입 (temp의 주소값 110)
3: temp를 *temp로 참조할 수 없다. 왜냐하면 temp는 일반변수이기 때문에
4: 포인터 변수 p가 가르키는 값을 p_temp에 대입(*p의 값은 data의 주소) 또한 *p_temp에 대입하는 것이기 때문에
*p라고 하더라도 정수 100이 되기 때문에 자료형이 맞지 않는다. 그래서 형변환(int*)이 필요
5: *p_temp로 data의 주소값을 참조하여 값 변경
근데 뭔가 좀 이상하다. 내가 처음에 설계했던 그림이 아니다. *p_temp를 선언하지 않고 만들고 싶은데 어떻게 하면 좋을까?
그럼 이차포인터를 써보자.
int main()
{
int data;
int **p;
int temp;
temp=(int)&data;
p=(int**)&temp; //&temp는 (int*)이다 그러나 p는 **이기 때문에 형변환을 해준다.
**p=6; //data=6;
//==*(*p)=6;
}
코드는 이렇게 나온다. 코드만 봐서는 이해가 힘드니 그림과 같이보자.
-data의 주소값은 100이라고 가정
-이차 포인터 변수 p 선언
-일반 변수 temp 선언
1: 형변환으로 인해 temp에 data의 주소값을 대입(상수 값으로 대입)
2: **p에 temp의 주소값을 형변환 대입 (temp의 주소값 110)
3: **p를 구분지어서 해석->*(*p)그러면 ()안의 *p를 먼저 해석-> *p==100
4: *(*p)에서 *p가 100이니까 *(100)-> 주소값 100은 data의 주소값 => 값이 6으로 바뀜
위에 코드랑 같은 코드가 아래 코드이다
int main()
{
int data;
int **p;
//int temp;
//temp=(int)&data;
//p=(int**)&temp;
int *p_temp;
p_temp=&data;
p=&p_temp;
**p=6; //data=6;
}
이렇게 하면 형변환을 사용할 필요가 없다. 왜냐하면 일반변수를 가르키는건 일차 포인터이다.
그리고 일차 포인터를 가르키는건 이차 포인터이고 이차포인터를 가르키는건 3차 포인터이다.
이렇게 한단계씩 연관이 있다고 생각하면 된다.
+)주소는 4byte면 다 대입 가능 대신 형변환 해야함
●동적 메모리 할당
정적메모리(배열)는 []안에 상수만 허용된다. 변수가 들어가면 안된다.
아래는 예시 코드이다.
#include<stdio.h>
int main()
{
short data[5];
for(int i=0; i<5; i++)
{
scanf_s("%d",data+i);
}
for(int i=0; i<5; i++)
{
printf("%d ",data[i]);
}
}
그럼 위의 코드를 포인터로 바꾸면 어떻게 될까?
#include<stdio.h>
#include<malloc.h>
int main()
{
short *p_data;
int cnt=5;
p_data=(short*)malloc(sizeof(short)*cnt); //10byte 메모리 할당
if(p_data!=NULL) //동적할당 성공 여부 체크
{
for(int i=0; i<cnt; i++)
{
scanf_s("%d",p_data+i);
}
for(int i=0; i<cnt; i++)
{
printf("%d ",*(p_data + i));
}
}
free(p_data); //할당 해제
}
포인터 변수 자체는 stack에 존재(일반 변수 포함), 위의 코드에서는 stack 메모리에 8byte 존재
malloc 동적할당은 heap에 존재, 위의 코드에서는 10byte존재
동적할당을 하면 크기에 대한 유도리가 생기고 heap은 최대 2G를 사용할 수 있다. 대신에 stack에 비해 동작속도는 느려진다.
단점으로는 메모리할당을 했을 때 너무 적은 용량만 사용한다면 손해이다.
이제 이차 배열로 실습을 해보자.
아래와 같은 배열이 있으면 값을 수정해야할 때 다시 빌드하고 코드를 전체 수정해야한다.
short data[3][4]; //정적 메모리
동적메모리로 바꾸면 어떻게 될까? 2차 포인터로 바꾸면 된다.
근데 난 실력이 딸린다.
대처 방안을 알아보자.
아래 코드와 같이 한 개만 상수로 고정시켜서 만들어보자.
short data[상수][변수];
#include<stdio.h>
#include<malloc.h>
int main()
{
short *p[5];
int cnt=3;
for(int i=0; i<5; i++)
{
p[i]=(short*)malloc(sizeof(short)*cnt); //2*cnt byte 메모리 할당
if(p[i]!=NULL) //동적할당 성공 여부 체크
{
for(int j=0; j<cnt; j++)
{
*(p[i]+j)=0;
}
}
}
for(int i=0; i<cnt; i++)
{
if(p[i]!=NULL) //동적할당 성공 여부 체크
{
free(p[i]); //할당 해제
}
}
return 0;
}
포인터 배열 short*p[5]를 각각 cnt만큼 동적할당해준다. 이것을 그림으로 표현하면
또한 *(p[i]+j)=0; 코드를 이용하여 안에 있는 쓰레기 값들을 0으로 대입하는 코드가 있고
마지막에는 p[i]가 없어질 때까지 free(p[i])를 싹 다 해준다.
이제 아래 코드처럼 해보겠다 위와는 다른점이 상수로 고정시킨 부분이 뒤쪽이다.
short data[변수][상수];
#include<stdio.h>
#include<malloc.h>
int main()
{
short *(p)[3];
int cnt=5;
//이름을 뺀 나머지가 자신의 타입이 된다.short *(p)[3](형변환임);에서 p를 뺀 나머지
//(short[3])가 자기 자료형이 된다.
p=(short(*)[3])malloc(sizeof(short[3])*cnt); //6*cnt byte 할당
if(p!=NULL)
{
for(int i=0; i<cnt; i++)
{
for(int j=0; j<3; j++)
{
(*(p+1))[j])=0; //p[i][j]=0;과 동일
}
}
}
free(p); //할당 해제
return 0;
}
아래 그림으로 살펴보자.
처음 동적할당을 시작할 때 short(*)[3] byte 크기만큼 해준다. 이 크기는 6byte이다. 왜냐하면 short[3]의 크기를 가르키기 때문이다. 그런 후 cnt만큼 곱해주면 p는 30byte의 크기만큼 동적할당이 된다.
그 후 p가 NULL이 아니면 반복문과 (*(p+1))[j])=0; 코드를 통해 0으로 초기화 시켜준다.
(*(p+1))[j])=0;는 p가+0이면 0번째 칸이고 +1을 하면 6byte만큼 커진다.
그런 후 동적할당은 1번만 했으므로 free(p);를 해준다.
+)
int data 의 자료형은 int
char a[5]의 자료형은 char[5]
결론: 자료형은 이름을 뺀 나머지가 자료형임
이 코드들은 전부 10번정도 타이핑 해야한다. 최소..
이제 2차 포인터를 이용한 2차 배열을 구현해보자.
#include<stdio.h>
#include<malloc.h>
int main()
{
short **p;
int g_cnt=5,s_cnt=3;
pp=(short**)malloc(sizeof(short*)*g_cnt);
if(pp==NULL)
return -1;
for(int i=0; i<g_cnt; i++)
{
*(pp+i)=(short*)malloc(sizeof(short)*s_cnt);
if(*(pp+i)!=NULL)
{
for(int s_i=0; s_i<s_cnt; s_i++)
{
*(*(pp+i)+s_i)=0;
}
}
}
for(int i=0; i<g_cnt; i++)
{
if(*(pp+i)!=NULL)
free(*(pp+i));
}
free(pp); //할당 해제
return 0;
}
매우 복잡한 코드가 나왔다.
그림으로 보자.
1: **pp는 이차 포인터 변수 즉 지역변수이기 때문에 stack 메모리에 존재한다.
2: short형은 2byte지만, short*는 포인터 형이기 때문에 4byte 그래서 동적할당하면 *g_cnt를 통해 20byte 생성
3: 3번과 같은 표가 만들어지고 시작번지가 100번지라고 했을 때 **pp는 100번지라는 주소를 가지게 된다.
4: *(pp+i)를 반복문으로 3번에서 만든 표(포인터 아님 그냥 개당 4바이트 배열임)로 각각 접근할 수 있게 만든다.
5: 동적할당을 통해 6byte의 메모리를 구현
6: 한칸당 2byte의 메모리가 생성 총 6byte
7: 각각의 pp+i가 6번의 메모리를 가짐
8: : *(*(pp+i)+s_i)=0;을 통해 새로 만들어진 메모리에 0 대입
for(int i=0; i<g_cnt; i++)
{
if(*(pp+i)!=NULL)
free(*(pp+i));
}
free(pp);
마지막 코드에서 이렇게 쓴 이유는 먼저 free(pp)를 해버리면 1번에 있는 stack영역의 포인터 변수가
사라지기 때문에 반복문으로 동적할당했던 메모리들은 쓰레기들이 되어 메모리를 잡아먹는다.
한마디로 엄마 없는 고아이다.
그렇기 때문에 반복문으로 파생된 메모리들을 먼저 free해준 후 마지막에 pp를 free해준다.
그러나 이렇게 사용하면 메모리가 훨씬 많이 들어간다. 총 배열을 구현하는데만 54byte를 사용한다. 그래서 적은 용량을 사용하게 되면 배보다 배꼽이 더 커질 수 있다.
'Tipslab 강좌 복습 > 김성엽 선생님 c 강의 복습' 카테고리의 다른 글
c 언어 온라인 무료강좌 9차시 정리(마지막) (0) | 2021.02.05 |
---|---|
c 언어 온라인 무료강좌 8차시 정리 (0) | 2021.02.03 |
c 언어 온라인 무료강좌 6차시 정리 (0) | 2021.01.27 |
c 언어 온라인 무료강좌 5차시 정리 (0) | 2021.01.21 |
c 언어 온라인 무료강좌 4-2차시 정리 (0) | 2021.01.20 |