❗ A와 B는 별도의 메모리이며 값만 복사가 된다.
배열의 이름은 첫 번째 주소값을 가리키고 있으므로 포인터라고 할 수 있다.
#include<stdio.h>
void func(int* pArr);
int main(void)
{
int arr[] = { 1,2,3,4,5 };
int i;
func(arr);
for (i = 0; i < 5; i++)
{
printf("배열의 요소 출력 : %d\n", arr[i]);
}
return 0;
}
void func(int* pArr)
{
int i;
for (i = 0; i < 5; i++)
{
printf("함수 안에서 배열의 요소 출력 : %d\n", *(pArr + i));
}
printf("\n");
}
출력결과
함수 안에서 배열의 요소 출력 : 1
함수 안에서 배열의 요소 출력 : 2
함수 안에서 배열의 요소 출력 : 3
함수 안에서 배열의 요소 출력 : 4
함수 안에서 배열의 요소 출력 : 5
배열의 요소 출력 : 1
배열의 요소 출력 : 2
배열의 요소 출력 : 3
배열의 요소 출력 : 4
배열의 요소 출력 : 5
함수의 전달인자 arr은 주소값을 넘기고 있다.
형식 인수의 형태가 int *pArr임을 볼 수 있다. 주소값을 넘겨받기 위해서는 포인터 형태로 받아야 하기 때문이다.
func로 배열의 모든 요소의 합을 리턴
#include<stdio.h>
int func(int* pArr, int size);
int main(void)
{
int arr[] = { 1,2,3,4,5 };
int sumArr, sizeArr;
sizeArr = sizeof(arr) / sizeof(int);
sumArr = func(arr, sizeArr);
printf("배열의 총 합은 :%d\n", sumArr);
return 0;
}
int func(int* pArr, int size)
{
int i, sum = 0;
for (i = 0; i < size; i++)
{
sum += *(pArr + i);
}
return sum;
}
주소를 복사했다 = 같은 메모리를 가지고 있다.
#include<stdio.h>
void callValue(int b);
int main(void)
{
int a = 1;
callValue(a);
printf("실인수 a의 출력 : %d\n", a);
return 0;
}
void callValue(int b)
{
b = b + 3;
printf("형식인수 b의 출력 : %d\n", b);
}
출력결과
형식인수 b의 출력 : 4
실인수 a의 출력 : 1
실인수 a의 값을 형식인수 b로 넘겨받으면서 별도의 메모리상에 복사가 일어난다.
두 개의 값을 바꾸는 예제
#include<stdio.h>
void Swap(int a, int b);
int main(void)
{
int x = 10, y = 20;
printf("초기값 x = %d, y = %d\n", x, y);
Swap(x, y);
printf("함수 밖에서 변경 후 x = %d, y = %d\n", x, y);
return 0;
}
void Swap(int a, int b)
{
int temp;
temp = a;
a = b;
b = temp;
printf("함수 안에서 변경 후 a = %d, b = %d\n", a, b);
}
출력결과
초기값 x = 10, y = 20
함수 안에서 변경 후 a = 20, b = 10
함수 밖에서 변경 후 x = 10, y = 20
실인수 x,y는 각각 형식인수 a,b에 값이 복사된다.
메모리 구조를 통해 이해해보자.
함수 호출 시 전달인자로 메모리 접근에 사용되는 주소값을 전달
#include<stdio.h>
void callReference(int* b);
int main(void)
{
int a = 1;
callReference(&a);
printf("실인수 a의 출력 : %d\n", a);
return 0;
}
void callReference(int* b)
{
*b = *b + 3;
printf("형식인수 b의 출력 : %d\n", *b);
}
출력결과
형식인수 b의 출력 : 4
실인수 a의 출력 : 4
참조 호출 메모리 구조
b ⇒ 0x100
*b ⇒ 1
차이점
값에 의한 복사 : 각각 다른 메모리를 가짐
참조에 의한 복사 : 주소값을 복사한 것이기 때문에 메모리를 공유함
두 개의 값을 바꾸는 예제
#include<stdio.h>
void Swap(int *a, int *b);
int main(void)
{
int x = 10, y = 20;
printf("초기값 x = %d, y = %d\n", x, y);
Swap(&x, &y);
printf("함수 밖에서 변경 후 x = %d, y = %d\n", x, y);
return 0;
}
void Swap(int *a, int *b)
{
int temp;
temp = *a;
*a = *b;
*b = temp;
printf("함수 안에서 변경 후 a = %d, b = %d\n", *a, *b);
}
출력결과
초기값 x = 10, y = 20
함수 안에서 변경 후 a = 20, b = 10
함수 밖에서 변경 후 x = 20, y = 10
주소값 앞에 타입이 붙는다면 포인터 선언을 하는 것!
실인수 x,y는 값을 a와 b에 넘길 때 주소값 &x, &y를 넘기기 때문에 포인터 변수 a와 b에는 주소값이 대입된다.
메모리 구조를 통해 이해해보자.
int **pp
단일 포인터의 개념부터 알아보자.
int a = 5;
int *p;
p = &a;
포인터 p가 변수 a의 주소를 가리키고 있다.
a는 p가 p는 pp가 가리키고 있다.
포인터 변수 p와 이중 포인터 변수 pp를 통해서 a에 접근할 수 있다.
포인터의 주소값을 가지고 있는 것이 이중 포인터.
함수의 리턴값이 없는 경우 void형으로 선언하는 정도
#include<stdio.h>
int main(void)
{
void a;
return 0;
}
error C2182: 'a' : 'void' 형식을 잘못 사용했습니다.
void형은 메모리를 얼마나 할당할 지 알 수 없기 때문이다.
int main(void)
{
void* a;
return 0;
}
void a 문장의 오류에 막연히 오류가 날 것이라고 예상했지만 오류가 뜨지 않는다.
void형 포인터는 메모리 주소값을 저장하기 위한 포인터형이다.
포인터 변수는 무조건 4바이트 메모리가 할당되므로, 4바이트 메모리가 할당된다.
#include<stdio.h>
int main(void)
{
void* a;
int b = 1234;
a = &b;
printf("%d\n", *a);
return 0;
}
실행결과: error C2100: 간접참조가 잘못되었습니다.
void형 포인터인 a는 정수형 b의 주소값을 받고 있지만 주소값만 넘겨받았을 뿐이지 a입장에서는 몇 바이트만큼의 메모리를 읽어야 할 지 알 수 없다.
#include<stdio.h>
int main(void)
{
void* a;
int b = 1234;
a = &b;
printf("%d\n", *(int)a);
return 0;
}
출력결과
1234
*a 출력 시 *(int*)a를 사용
참조하고 있는 변수 b가 정수이므로 int형, 포인터형이므로 형변환 자료형은 int*, a가 가리키고있는 변수의 값을 참조하므로 변수명 앞에 * 연산자 붙이기
*((int*)a)와 같은 표현
#include<stdio.h>
int main(void)
{
void* a;
double b = 3.14;
a = &b;
printf("%f\n", *(double*)a);
return 0;
}
출력결과
3.140000
함수 포인터의 선언 방법은 다음과 같다.
자료형(*함수 포인터 이름)(인자 목록)
함수명 앞에 *만 붙여주면 함수 포인터가 선언된다.
int(*func)(int a);
함수 포인터도 포인터이므로 주소값을 저장한다.
포인터: 무조건 주소값 저장
#include<stdio.h>
int Add(int a, int b);
int Min(int a, int b);
int main(void)
{
int a, b, sel, result;
int (*fPtr)(int a, int b);
while (1)
{
printf("다음 중 선택하시오 (1. 덧셈 2. 뺄셈 3. 종료 ) : ");
scanf_s("%d", &sel);
switch (sel)
{
case 1:
fPtr = Add;
break;
case 2:
fPtr = Min;
break;
case 3:
return 0;
default:
break;
}
printf("두 정수를 입력하시오 : ");
scanf_s("%d%d", &a, &b);
result = fPtr(a, b);
printf("결과 : %d\n", result);
}
return 0;
}
int Add(int a, int b)
{
return a + b;
}
int Min(int a, int b)
{
return a - b;
}
출력결과
다음 중 선택하시오 (1. 덧셈 2. 뺄셈 3. 종료 ) : 1
두 정수를 입력하시오 : 10 20
결과 : 30
다음 중 선택하시오 (1. 덧셈 2. 뺄셈 3. 종료 ) : 2
두 정수를 입력하시오 : 30 10
결과 : 20
다음 중 선택하시오 (1. 덧셈 2. 뺄셈 3. 종료 ) : 3
코드를 하나 만들어놓고 런타임에 결정을 하게 해주면 효율적인 프로그램을 만들 수 있다.
NULL 포인터란 아무것도 가리키지 않는 포인터를 말한다.
아무것도 가리키지 않는다는 의미는 포인터 변수에 아무런 주소값도 저장되어 있지 않는다는 의미이다.
NULL은 아무것도 가리키지 않는다는 의미를 가지고 있지만, 실전에서는 함수의 동작 에러 체크 용도로 사용한다.
char *p = Func();
if(p == NULL)
{
// 에러 처리
}
else
{
// 수행 처리
}