C언어 복습 16 (포인터)
참고: 정덕 - <컴맹을 위한 C언어>
♨ 개인적 해석이 다분한 글임으로, 인지하지 못한 오류가 있을 수 있습니다 ♨
비트는 1, 0이 들어갈 수 있는 정보저장의 최소단위
메모리는 비트들이 1차원적으로 붙어있는 형태
메모리는 1바이트(8비트)마다 주소가 매겨져 있다
-> 포인터는 "메모리주소"를 의미 / 메모리주소를 저장할 수 있는 변수가 포인터 변수
▣ 변수의 주소를 사용하는 방법
- 주소연산자(&)를 변수 이름 앞에 붙여 메모리상 어느 위치에 있는지 알 수 있다
-> 어디서 많이 본 듯 하다면 scanf에서 사용자에게 입력을 받을 시 사용하던 그 친구이다
scanf는 선언된 변수의 주소를 참조하여 입력값을 넣는 것이였다
- 변수의 메모리주소(포인터)를 출력한 결과가 숫자+알파벳인 이유 ->16진수 형식
2진수를 3개 혹은 4개씩 끊어서 8진수(3개)/ 16진수(4개)로 출력
예) 10진수 65 = 2진수 01000001 이를 4개씩 끊으면 0100 / 0001 16진수로 표현하면 41이다
▣ 포인터가 필요한 이유
- 1. 함수의 인자로 배열을 넘기고 싶을 때
만약 A라는 함수 내에서 B라는 함수를 호출할 때 B함수에 필요한 인자들이 B함수의 영역 앞으로 복사
(B함수가 인자로 넘겨진 값을 사용할 수 있게 하기 위해)
예) 외부함수 B - int Num(인자)를 외부함수 A에서 호출/사용
이때 배열을 인자로 넘길 수 있게 되면 함수를 한 번 호출할 때 마다 배열에 있는 방대한 데이터가 복사
-> 프로그램 실행 속도 저하
-> 배열은 인자로 넘길 수 없게 되어 있다
BUT! 인자로 배열의 메모리주소(포인터)를 넘겨주면 배열 자체가 아닌 해당 주소를 사용하여
배열에 있는 데이터 사용 가능
- 2. 다른 함수의 영역 안에 있는 변수의 값을 바꾸고 싶을 때
이 값의 결과가 a = b, b = a (a, b를 입력하면 b, a가 출력)일 것 같지만 그렇지 않다
왜이러지???
-> swap함수에 '인자'를 넘겨 호출할 경우 인자가 swap함수 영역 앞으로 복사
swap함수가 바꾼 것은 swap함수의 영역 앞에서 복사된 값일 뿐
인자로 넘겨진 실제 변수(num1, num2)에 있는 값은 바뀌지 않는다
이때 변수의 주소(포인터)를 인자로 받으면
swap함수에서 해당 변수의 주소로 찾아가 변수의 값을 직접 수정하는 것이 가능
※ Call By Value // Call By Reference
함수 호출 시 함수 인자에 값을 넘기는 2가지 방법
앞선 상황에서 swap함수가 했던 것은 num1, num2 변수의 '값'에 초점을 두고 그 값을 복사해 사용하는
Call by Value
만약 num1, num2라는 변수에 초점을 두고, 각각의 포인터를 사용하여 변수를 '참조'하는
Call By Reference
※ 게임, 동영상 프로그램 등은 메모리를 크게 확보해서 다량의 데이터 처리
but 프로그램 실행시 확보되는 영역에는 제한 -> 힙 영역 필요
*힙 영역: 메모리 안에서 마음대로 확보해서 사용할 수 있는 영역
=> 힙 영역에 확보된 데이터를 읽고 쓰기 위해서 메모리주소(포인터)를 이용해야 한다
▣ 포인터 변수의 선언과 초기화
- 포인터 변수를 선언할 떄 필요한 정보
1. 포인터 변수의 이름
2. 포인터 변수가 가리킬 데이터의 자료형
위에서 int는 25000~25003번지까지 4바이트 메로리를 차지
그 시작주소인 25000이 int 변수의 주소
-> 25000이라는 숫자를 저장하고 있는 포인터 변수는 이 int변수를 '가리킨다'고 표현 // 진짜?
- 포인터 변수의 선언 방법
포인터 변수는 포인터가 가리킬 자료형 뒤에 애스터리스크" * "기호(별표)를 붙여 선언
자료형* 변수이름;
예) char* str;
- 포인터 변수의 초기화 방법
포인터 변수를 초기화하기 위해서 주소연산자(&)를 이용, 변수의 주소를 포인터 변수에 대입
① double number=971.002;
② double* pnumber=&number;
위 코드에서 ②pnumber는 ①number을 가리키는 'double형 포인터' 변수(=double형 변수 number의 시작주소)
포인터가 아무것도 가리키지 않는다는 표시
-> int* pointer=NULL;
NULL은 아무것도 없다는 의미
- 포인터 변수의 크기
무엇을 가리키든 포인터 변수의 크기는 항상 동일
-> 32비트 환경에서는 4바이트(32비트), 64비트 환경에서는 8바이트(64비트)
▣ 포인터의 가감산
만약 위 코드의 결과(int형 변수 a의 주소값)가 100이라면
printf("%p", pointer+1) = 104이다
(-> int값이 4이기에 100~103이 int의 크기이고, 이에 +1을 하니까 104가 출력)
아래의 코드는 위의 코드와 동일한 결과가 출력된다
int a;
printf("%p", &a);
a의 주소를 담고 있는 'pointer'와 '&a'가 가리키는 것이 동일하기 때문
▣
- 책(컴맹을 위한 C언어)에 의하면,
배열 생김새가 A[b]일때
A와 b를 더해서 나온 메모리주소에 있는 데이터를 참조하는 것(A+b의 메모리주소 참조)이기에 결과가 동일하다
예외상황 3가지(이건 책에 잘 나와있다;)를 제외하면,
'배열이름'(arr) 을 참조하려고 시도할 때 배열의 첫번째 요소(1.0)의 포인터가 참조 (= arr은 arr[]의 시작주소 포인터)
※ 이와 같이 배열이름을 가지고 배열을 포인터처럼, 포인터를 배열처럼 사용하는 것 처럼 보일 때가 있다
포인터를 선언하고 거기에 배열이름을 넣으면 (바로 위 코드에서라면, double* arrptr을 선언하고 arrptr = arr;)
오류가 없을 뿐더러, 포인터 변수와 인덱스로 배열의 값을 참조할 수 있기 때문이다
이는 배열변수(이름)가 포인터 상수이기 때문이다.
즉 배열변수 = 값을 이미 갖고 있고, 새로 할당받지 못하는 포인터 라서 포인터로서 사용할 수도 있는 것이다
▣ 역참조
- 데이터를 직접 참조하지 않고 포인터를 이용해 참조하는 것 (≒간접참조)
- 역참조연산자 : " * "(애스터리스크 기호)
예)
-> double형 포인터 변수 pnum에는 double형 변수인 num의 주소가 들어가 있다
pnum 앞에 역참조연산자를 붙여 데이터를 쓰거나 읽으면
pnum이 가리키는 num의 데이터가 읽히거나 써진다
- swap함수 만들기(두 변수에 있는 값을 스왑하는 방법)
1. 하나의 변수를 새로 선언한다
2. 변수 A에 있는 데이터를 새변수로 옮긴다
3. 변수 B에 있는 데이터를 변수 A로 옮긴다
주의사항: 변수의 값을 직접 넘겨 호출하면 호출된 함수가 변수의 값을 저장 할 수 없다
변수의 주소를 넘겨주면 역참조로 수정이 가능
swap함수를 호출 할 때 int가 아닌 int형 포인터를 넘겨주어야 한다
▣ 동적 배열 만들어보기
강제 형 변환 복습 강제 형 변환: 자료형을 강제로 바꾸는 것
-> 바꾸려는 데이터 앞에 " (자료형) " 을 붙여서 바꾼다
예) int num=123; float realnum= (float)num;
- 동적?
동적할당이란? : 프로그램 실행 중에 프로그램에서 확보하지 않았던 메모리를 확보해주는 것
- malloc함수를 이용한 메모리 확보
①역 할: malloc 함수를 이용하면 힙 영역을 원하는 만큼 확보
(힙 영역: 자유롭게 확보해서 사용할 수 있는 메모리 영역);
②헤더 파일: stdlib.h
③인 자: 확보할 바이트 수, 자료형은 size_t(양의 정수)
④반 환 값: 메모리 확보에 실패시 -> NULL 반환
성공시 -> 메모리의 첫 주소(포인터)를 void*형으로 반환
- free함수를 이용한 메모리 해제
메모리 확보 후 해제하지 않을 시 프로그램 종료시까지 메모리차지
-> 반드시 해제 :" free(포인터 변수) "로 해제 가능
▣ 2차원 포인터
포인터가 포인터를 가리키는 것
2차원 포인터 -> 1차원 포인터의 주소
1차원 포인터 -> 데이터의 주소
▣ 2차원 포인터가 필요한 이유
2차원 포인터는 함수 내에서 1차원 포인터의 값을 수정할 때 용이
예) 동적 메모리 할당을 알아서 해주는 함수를 만들고 싶어서 아래와 같이
void ddalloc(int*a, size_t n)
{
a = malloc(sizeof(int) * n);
}
이렇게 만들면 결과는 실패
<-?: a, b, tep으로 a, b 값 맞바꿀때 처럼, dalloc함수 안에 호출된 인자 (int형 포인터) a는 복사된 것
(Call by Value/실질적 수정X)
∴ 2차원 포인터를 활용해서 1차원 포인터를 혼내주면 값을 참조 호출할수 있다
▣ 2차원 포인터의 선언과 초기화
- 1차원 포인터 선언 : 자료형* 변수이름;
=> 2차원 포인터 선언: 자료형** 변수이름;
예) int a =10;
int* aa= &a;
int**aaa=&aa;
비워두고 싶을때는(선언만 할때) int*** aaa=NULL;
▣ 2차원 포인터로 역참조 하기
(위에서 실패 했던 동적 메모리 할당을 알아서 해주는 함수 만들기)
2차원 포인터에 역참조연산자(*)를 붙이면 1차원 포인터를 참조
+ 역참조연산자를 하나 더 붙이면 원시자료형 참조 가능
예)
▣ void 포인터
- void 포인터? : 포인터는 포인터지만 어떤 자료형의 데이터를 가리킬지 정해지지 않은 것
- void 포인터로 역참조는 불가(자료형이 정해지지 않았기에) -> 강제 형변환 필요