포인터 기초
Address(주소)
- Memory(메모리)는 저장 장소의 위치를 나타내는 Address 값을 가진다.
- 이 Address(주소)는 1Byte(바이트)마다 1씩 증가하는 연속적인 번호로 구성된다.
- Address 값은 보통 16진수로 표현한다.
Address Operator(주소 연산자) &
- 변수의 Address 값을 얻기 위해서는 변수 앞에 Ampersand(&) 기호를 사용하는 Address Operator를 이용한다.
- 변수
age
가 있다면,&age
는age
변수의 Address 값을 반환한다. - 이 Address 값을
printf
함수로 출력하려면%p
변환 명세를 사용한다. - Address 값을 이용하면 더 편리하고 유연한 프로그램 작성이 가능하지만, 코드가 복잡하고 어려워지는 단점이 있다.
address.c 예제
아래 코드는 char
, int
, double
자료형 변수들의 값과 Address를 출력하는 예제이다.
#include <stdio.h>
int main(void)
{
char ch = 'a';
int num = 100;
double real[2] = {3.14, 1.87};
printf("char 변수(ch) 값 %6c의 주소는 %p이며, 십진수로 %8u이다.\n", ch, &ch, &ch);
printf("int 변수(num) 값 %6d의 주소는 %p이며, 십진수로 %8u이다.\n", num, &num, &num);
printf("\n변수 ch와 변수 num의 주소 값의 차이는 %u이다.\n\n", (unsigned)&ch - (unsigned)&num);
printf("double 변수(real[1]) 값 %6.2F의 주소는 %p이며, 십진수로 %8u이다.\n", real[1], &real[1], &real[1]);
printf("double 변수(real[0]) 값 %6.2F의 주소는 %p이며, 십진수로 %8u이다.\n", real[0], &real[0], &real[0]);
printf("\n변수 num과 변수 real[0]의 주소 값의 차이는 %u이다.\n\n", (unsigned)&num - (unsigned)&real[0]);
return 0;
}
- 변수들의 Address 값은 서로 다르며, 그 차이는 각 자료형의 크기와 관련이 있다.
Little Endian(리틀 엔디안)과 Big Endian(빅 엔디안)
Memory에 여러 Byte로 구성된 데이터(예: int
형)를 저장하는 방식에는 두 가지가 있다.
- Little Endian: 데이터의 가장 작은 단위(최하위 바이트)가 낮은 Address에 저장되는 방식이다.
- Big Endian: 데이터의 가장 큰 단위(최상위 바이트)가 낮은 Address에 저장되는 방식이다.
Pointer Variable(포인터 변수)
- 일반 변수와 달리 Memory의 Address 값만을 저장할 수 있는 특별한 변수이다.
- Pointer Variable(포인터 변수)를 사용하면 프로그램이 간결하고 효율적으로 될 수 있다.
예를 들어, int i = 3;
이라는 변수가 있고, 이 변수의 Address가 12FF7C
라고 한다면, 이때 int *pi = &i;
와 같이 선언할 때 pi
라는 Pointer Variable은 변수 i
의 Address 값인 12FF7C
를 저장하게 된다.
Pointer Variable의 선언과 값
-
Pointer Variable은 가리키는 변수의 자료형에 맞춰 선언해야 한다.
*
(Asterisk) 기호는 해당 변수가 Pointer Variable임을 나타낸다.int *ptr;
:ptr
은int
형 변수의 Address를 저장하는 Pointer Variable이다.*ptr
자체는int
형 변수로 생각할 수 있다.
-
여러 개의 Pointer Variable을 선언할 때는 각 변수명 앞에
*
를 붙여야 한다.int *ptr1, *ptr2, *ptr3;
-
Pointer Variable은 Address 값만 저장할 수 있으며, 선언과 동시에 특정 변수의 Address로 초기화할 수 있다.
int i = 3;
int *ptr = &i;
-
위 코드에서
ptr
은i
의 Address를 가지므로,*ptr
은 변수i
자체를 의미하게 되어*ptr
의 값은3
이다.
Dereference Operator(역참조 연산자) *
- Pointer Variable 앞에
*
를 붙이면, 해당 Pointer Variable이 가리키는 Memory 공간에 저장된 값에 접근할 수 있다. 이것을 Dereference(역참조)라고 한다. - 예를 들어,
*ptr = i + 2;
라는 구문은ptr
이 가리키는 변수(즉,i
)의 값을i + 2
로 변경하라는 의미이다.
배열과 포인터
- 배열의 이름은 그 자체로 배열의 첫 번째 원소의 Address를 나타내는 주소 상수(constant)이다.
int point[] = {95, 88, ...};
에서point
는&point[0]
과 같다.
- 따라서 배열 이름을 Dereference하면 첫 번째 원소의 값이 된다.
*point
는point[0]
과 같다.
- 포인터 연산을 통해 다른 배열 원소에도 접근할 수 있다.
point + 1
은 두 번째 원소(point[1]
)의 Address를 의미한다. (&point[1]
)- 이때 실제 Address 값은
(point의 Address) + sizeof(int)
만큼 증가한다.
- 일반적으로
*(point + i)
는point[i]
와 동일하다.
함수 인자로서의 포인터
함수의 인자로 배열이나 포인터를 전달하는 방식을 **Call by Address(주소에 의한 호출)**라고 한다.
배열 합계 함수 예제
배열의 합을 구하는 함수는 다음과 같이 두 가지 방식으로 선언할 수 있다.
-
배열 형태로 인자 받기:
int sumaryf1(int ary[], int SIZE)
- 함수 내부에서
ary[i]
또는*(ary + i)
형태로 원소에 접근할 수 있다.
- 함수 내부에서
-
포인터 형태로 인자 받기:
int sumaryf2(int *ary, int SIZE)
- 함수 내부에서
*ary++
와 같이 포인터를 직접 증가시키며 원소에 접근할 수 있다.
- 함수 내부에서
함수를 호출할 때는 배열의 이름이나 첫 번째 원소의 Address를 넘겨준다.
sum = sumary(point, 6);
sum = sumary(&point[0], 6);
// 위와 동일
Call by Value(값에 의한 전달) vs Call by Address
- Call by Value: 일반 변수를 인자로 넘기는 방식으로, 함수 내에서 인자 값을 변경해도 원본 변수에는 영향을 주지 않는다. 값의 복사가 일어나기 때문이다.
- Call by Address: 변수의 Address를 인자로 넘기는 방식으로, 함수 내에서 포인터를 Dereference하여 원본 변수의 값을 직접 수정할 수 있다.
Swap 함수 비교
- Call by Value 사용 시: 두 변수의 값이 바뀌지 않는다.
- Call by Address 사용 시: 포인터를 이용해 원본 변수의 값을 직접 교환하므로 값이 성공적으로 바뀐다.
포인터 연산의 제약
-
배열의 이름은 주소 상수(constant)이므로, 값을 변경할 수 없다. 따라서
point++
와 같은 증감 연산은 컴파일 오류를 발생시킨다. -
하지만 배열의 Address를 다른 Pointer Variable에 복사한 후에는 해당 Pointer Variable을 증가시키는 것이 가능하다.
int point[] = { ... };
int *pi = point; // point는 상수, pi는 변수
sum += *(pi++); // 가능
문자열과 포인터
char
배열로 선언된 문자열에서&name[i]
는i
번째 문자부터 시작하는 새로운 문자열의 Address를 의미한다.char *pary[]
와 같이 문자 포인터 배열을 사용하면 여러 개의 문자열을 효율적으로 관리할 수 있다. 각 배열 원소는 문자열 리터럴의 시작 Address를 저장한다.
Command Line Argument(명령어 줄 전달인자)
-
프로그램을 실행할 때 명령어 줄(command line)을 통해 문자열 인자를 전달할 수 있다.
-
main
함수를int main(int argc, char *argv[])
형태로 선언하여 인자를 받는다.-
argc
: 전달된 인자의 개수 (프로그램 이름 포함) -
argv
: 인자 문자열들을 가리키는 포인터들의 배열 (char *[]
)argv[0]
: 프로그램 이름argv[1]
: 첫 번째 인자argv[2]
: 두 번째 인자, ...
-
-
개발 환경(예: Visual C++)에서는 프로젝트 속성에서 디버깅 시 사용할 Command Line Argument를 미리 설정할 수 있다.