일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 유료강좌
- 실습
- tips강좌
- 충무창업큐브
- mfc 실습
- 별찍기
- linux
- 김성엽
- MFC
- mysql
- 정처기 독학
- 마이크로소프트
- ListBox
- SSG
- Time
- do it c
- visual
- C
- Tipsware
- C언어
- 도움말
- MFC 예제
- 미가공
- 포트폴리오
- win32
- 정보처리기사
- 핵심 요약
- C++
- MyThread
- MyTread
- Today
- Total
History
c 언어 온라인 무료강좌 5차시 정리 본문
해당 게시물은 김성엽 선생님의 강의를 바탕으로 만든 게시물입니다.
●포인터 연습
먼저 알아야 할 상식을 말하겠다.
평소 우리는 자료구조의 스택을 공부할 때 bp의 0번째는 아래 그림과 같이 0부터 순차적으로 올라오게 되어있다.
그러나 컴퓨터 상에서는 여기 쓰여있는 번지가 거꾸로 되어있다. 그래서 우리는 평소 공부를 할 때 거꾸로 공부하는 습관을 가지고 해야 한다. 아래 그림은 예시이다.
이 그림처럼 밑의 번지가 더 크다.
자 그럼 이제부터 저번 게시물에 이어서 어떤 식으로 주소연산이 되는지 알아보자.
int a; 라는 변수가 선언되었다고 가정하자. 이 a에 0x12345678을 대입한다고 했을 때 값이 어떻게 들어갈까?
답은 아래 그림과 같다.
그러면 a에 0x3344를 대입하면?
표현한 값은 2byte이지만 int라는 자료형은 4byte 자료형이기 때문에 이 값은 실제로는 0x 00003344라는 값이 들어간다.
그러나 a의 값에 0x12345678을 대입하고 a=0x3344를 대입했다고 가정했을 때 포인터를 사용하지 않고 0x3344/5678 이 값이 나와야 한다면 어떻게 해야하는가?
답은 *((short*)&a+1)=0x3344
만약 &a라고 적으면 주소 값을 나타낸다. 이 &a는 int *a이기 때문에 포인터를 사용할 수 있다. 그러면 *(&a)가 가능하다. 그러면 a의 &의 주소는 bp-3이다. 그러나 bp-3 위치에서 바로 0x3344를 대입하면 0x1234/3344가 출력이 될 것이기 때문에 2byte만큼 bp를 옮겨줘야 한다. 그래서 +1을 해줘야 한다. 그러나 현재 상태에서 주소 연산에서 +1을 하면 byte만큼 커지기 때문에 실제로는 int자료형 값은 4만큼 증가한다.
그래서 short형으로 형 변환 후 +1을 하여 2byte만큼 bp를 옮겨준다. 그런 후 전체를 괄호로 묶어서 *를 써준다. 왜냐하면 *를 사용하지 않으면 값이 들어가는 것이 아니라 주소 값이 들어가기 때문이다. 이러한 과정을 거치면 내가 원하는 값인 0x3344/5678이 나온다.
그림으로 보고 다른 문제로 넘어가 보자.
이번 문제는 아래 표시된 부분에 0x7788의 값을 대입해보는 것이다.
답은 *(short*)(char*)&a+1)=0x7788이다.
a의 시작 주소는 78이 있는 bp-3이다. 그러면 난 1byte만 옮겨서 이동을 한 후 2byte만큼 데이터를 입력해야 하기 때문에 먼저 char형으로 형 변환 후 +1을 해주고 short형식으로 다시 형 변환을 하여 2byte만큼 데이터를 입력받을 크기를 정한다. 그 후 *를 사용하여 주소를 받는 것이 아닌 데이터를 받을 수 있게 역참조를 걸어서 값을 대입한다. 결국 char*은 주소 연산에 영향을 미치고 short*는 번지 지정의 역할을 한다. 그러면 아래와 같은 결과가 나온다.
자 이제 간단한 예제를 통해서 포인터를 쓰는 이유에 대해 알아보자.
아래 코드를 보자.
void swap(int a, int b)
{
int temp;
temp=a;
a=b;
b=temp;
}
int main()
{
int a=6, b=8;
swap(a,b);
return 0;
}
나는 지금 a와 b의 값을 바꾸는 함수를 만들었다. 과연 이 코드는 swap함수를 거치면 서로 값이 바뀔까?
정답은 안 바뀐다.
아래 그림처럼 bp가 a에 설정되어있지만,
swap함수에 가면 bp를 swap함수에 맞춰서 다시 설정한다.
그러나 swap함수에 맞춰서 bp를 설정하면 다시 main으로 못 돌아오니까
main함수에서 쓰던 bp를 push 해서 임시 저장한다.
그리고 swap함수의 bp를 재설정한다. 그러면 main의 a, b를 바꿀 수 있는 방법이 없다. 왜냐하면 현재 bp는 main bp가 아니기 때문이다. 그러면 어떻게 main의 a, b를 swap함수로 값을 바꿀 수 있을까?
정답은 주소 값을 이용하는 것이다. 왜냐하면 주소값은 바뀌지 않기 때문이다.
이러한 점 때문에 포인터를 사용한다.
그럼 포인터를 사용한 코드를 보자.
void swap(int *pa, int *pb)
{
int temp;
temp=*pa;
*pa=*pb;
*pb=temp;
}
int main()
{
int a=6, b=8;
swap(&a,&b);
return 0;
}
이렇게 주소값을 매개변수로 넘겨주어서 a와 b를 바꾸는 과정을 보자.
이렇게 a와 b의 값이 들어가 있고, pa와 pb에는 a와 b의 주소 값이 들어가 있다.
1번: temp=*pa -> *pa가 가지고 있는 주소 값의 값을 temp에 대입 temp==6
2번:*pa=*pb -> *pb가 가지고 있는 주소의 값을 *pa가 가지고 있는 주소의 값에 대입 *pa==8
3번:*pb=temp -> temp에 *pb의 주소 값의 값을 대입 *pb==6
결론: 고정된 주소를 가지고 swap을 사용하면 main안의 값에 영향을 줄 수 있다. 그래서 전역 변수를 가지고 프로그래밍을 하면 실력이 늘지 않는다.
+void 포인터
void는 전형적인 초보자 배려 문법이다.
그 이유는 형 변환이 필요가 없기 때문이다. 아래 코드는 잘 실행이 될까? 내가 볼 땐 아니다.
int main()
{
int data;
short*p;
p=&data;
}
왜냐하면 int와 short의 자료형이 맞지 않기 때문에 p=(short*)&data; 이렇게 형 변환을 해줘야 에러가 나지 않는다. 그렇게 *p=5; 이렇게 대입을 해주면 된다.
이제 void*의 예시를 보자. 이 코드는 형변환 없이 정상적으로 작동한다.
int main()
{
int data;
void*p;
p=&data;
}
왜냐하면 &data는 int*이다. 그러나 받는 쪽은 크기를 무시하는 void*이 있기 때문이다.
void*는 대상의 크기가 정해져 있지 않기 때문에 모든 형태의 주소를 다 받을 수 있다.
그러나 단점도 있다. void*는 값 연산이나 대입을 할 때마다 형 변환을 해줘야 하기 때문에 더 자주쓰는 연산에서 형변환을 계속해줘야 한다.
단점의 예시를 코드로 보자.
#include<stdio.h>
void swap(void *pa, void *pb)
{
short temp;
temp=*(short*)pa;
*(short*)pa=*(short*)pb;
*(short*)pb=temp;
}
int main()
{
int a=6,b=8;
swap(&a,&b);
return 0;
}
끔찍하고 귀찮아 보이는 코드가 나왔다. 이러한 점에서 void는 매개변수로 받기에는 편리하지만 사용하는 데 있어서 끔찍하다. 난 void*를 잘 사용안 할 것 같다.
●배열
지역변수를 합해놓은 것이다.
배열의 정의: 지역변수 여러 개를 합쳐 놓은 것
만약 int a, b, c, d, e,---z까지 26개의 변수를 선언하는 상황이 생겼다. 근데 배열을 사용하면 int arr [26]이렇게 쓰면 26개의 변수가 만들어진다. 얼마나 간편한가.
자 그럼 간단한 코드를 써보자
int main()
{
int a,data[5],b;
data[2]=5; //0 1 2 3 4 번지가 존재
return 0;
}
이러한 코드가 있는데
그림으로 그리면 이렇게 된다 변수는 선언된 순서대로 가 아니라 거꾸로 입력된다.
자 그러면 int data [2]=5; 이 코드가 실행되면 어떻게 값이 들어가는지 그림으로 보자.
처음 bp의 시작은 변수 a가 있는 부분이다. 현재 그림에서는 int a, data [5], b; 이렇게 변수들과 배열이 선언되어있다.
그리고 이 변수들은 1칸당 4바이트를 차지하기 때문에 a의 시작 주소는 bp-3이 된다.
그렇게 4의 배수씩 data의 시작 주소를 찾으면 bp-19가 되고 bp-19+8(==ptr [bp-11]) 은 data [2] 번째 주소가 된다. 그렇게 5가 대입이 된다.
ps) 위와 같이 a, data [5], b가 선언된 배열과 변수가 있을 때 data [-1] 번지에 5를 대입하겠다고 했을 때 이것은 오류가 있을까?
이건 에러일까?
우리는 에러라고 생각할 것이다. 그러나 컴파일러는 에러 처리를 하지 않는다. 왜냐하면 아까 위에서 적었던 bp를 이용한 수식을 사용하기 때문에 배열 인덱스를 초과해도 어딘가 위치가 될 것이다. 방금과 같은 상황이면 [-1] 위치면 data [-1]=5는 a변수 안에 데이터가 입력이 될 것이다.
여기서 의문이 생길 것이다.
-"아니~ 지역 변수는 선언할 때 무작위 주소에 있지 않냐~ 어떻게 배열과 변수들이 저렇게 따닥따닥 줄 서는 것처럼 붙어있냐~ 장난하냐~"
이렇게 생각할 수도 있다. 내가 저랬다.
그러나 원칙은 따닥따닥 붙어있는 게 맞다. 그러나 컴파일러가 바꾸기도 할 뿐이다.
그럼 이제 다른 사람들이 말할 것이다.
-"아니~ 내가 컴파일러로 테스트해봤는데 거짓말하지 마라~ 주소 값 다르게 나온다~"
그 이유는 디버그(Debug) 모드이기 때문이다.
그러나 릴리즈(Release) 모드를 사용하면 방금 말한 대로 따닥따닥 붙어서 나온다.
그래서 사람들이 디버그로 개발하다가 릴리즈로 변환하면 프로그램이 죽는 경우가 많다.
왜냐하면 디버그를 사용하면 메모리 침범이 생기지 않게 하기 위해 변수 사이마다 8~16바이트씩 여유 메모리를 주기 때문이다.
그럼 개발도 릴리즈로 하느냐?
아니다. 개발은 디버그로 하고 배포는 릴리즈로 한다. 왜냐하면 디버그와 릴리즈의 속도 차이가 어마어마하기 때문이다. 그렇기에 배포는 무조건 릴리즈 모드로 한다.
또한 프로그램의 오류가 나면 디버그로 오류를 고치고 다시 릴리즈로 배포한다.
근데 디버그로 바꿔서 오류를 해결하려고 하면 오류가 안 생겨서 애를 많이 먹는다고 한다.
그니까 만들 때 메모리 침범 안 생기게 잘 만들어라
'Tipslab 강좌 복습 > 김성엽 선생님 c 강의 복습' 카테고리의 다른 글
c 언어 온라인 무료강좌 7차시 정리 (0) | 2021.01.31 |
---|---|
c 언어 온라인 무료강좌 6차시 정리 (0) | 2021.01.27 |
c 언어 온라인 무료강좌 4-2차시 정리 (0) | 2021.01.20 |
c 언어 온라인 무료강좌 4-1차시 정리 (0) | 2021.01.19 |
c 언어 온라인 무료강좌 3차시 정리 (0) | 2021.01.18 |