C 언어가 다른 언어들과 다른 언어들과 다른점은 포인터를 이용해 메모리에 직접적으로 접근할 수 있다는 것이다
포인터란 변수의 메모리 주소를 가리키는 변수를 의미하는데, 변수가 선언되면 메모리에 해당 변수의 크기만큼 여러 개의 주소 값에 걸쳐 할당된다

메모리 100번지에 할당된 int형 변수 a를 그림으로 표현하면 위와 같고, 100번지부터 103번지 4바이트 전체를 a라는 이름으로 사용한다
주소 연산자 &
여기서 말하는 주소란 변수가 할당된 메모리 공간의 시작 주소를 의미하는데, 위의 int a를 예로 들면 a의 시작 주소는 100이고, 이 시작 주소를 알면 그 위치부터 해당 변수 크기만큼 메모리를 사용할 수 있다
이러한 주소는 & 연산자를 통하여 구할 수 있다
간접 참조 연산자 *
*은 두가지로 사용될 수 있는데, 하나는 포인터 변수를 선언할 때이고 다른 하나는 포인터 변수가 가리키는 변수를 사용할 때이다
한 가지 주의할 점은 포인터로 선언된 변수에는 주소 값만 들어갈 수 있다
간단한 예를 보자 (출력은 컴퓨터마다 다르다)
int a = 10;
int *pa;
printf("%d\n", pa); // 4194432 변수 pa의 주소값
printf("%d\n", *pa); // 17744 변수 pa에 저장된 쓰레기 값
pa = &a;
printf("%d\n", pa); // 6422308 변수 pa가 가리키는 변수의 주소값
printf("%d\n", &a); // 6422308 변수 a의 주소값
printf("%d\n", *pa); // 10 변수 pa가 가리키는 변수의 값
printf("%d\n", a); // 10 변수 a의 값
printf("%d\n", *pa == a); // 1 TRUE
포인터 변수 pa는 초기화하기 전에는 pa의 주소값을 나타내지만 a의 주소로 초기화하면 a의 위치를 나타낸다
포인터 특징
int형 변수는 4바이트, double형 변수는 8바이트인 것 처럼 변수들은 각 타입마다 크기가 다르다
포인터 변수의 크기는 어떨까?
포인터의 크기는 사용하는 컴퓨터 메모리의 최대 주소와 같다
이유는 간단한데, 포인터 변수에는 주소가 저장되어야 하므로 메모리 끝 번지까지 저장할 수 있어야 하기 때문이다
이러한 포인터의 크기는 sizeof() 연산자를 사용하면 확인할 수 있는데
char c;
double d;
char *pc = &c;
double *pd = &d;
printf("%d\n", sizeof(&c)); // 4
printf("%d\n", sizeof(&d)); // 4
printf("%d\n", sizeof(pc)); // 4
printf("%d\n", sizeof(pd)); // 4
printf("%d\n", sizeof(c)); // 1
printf("%d\n", sizeof(d)); // 8
내가 사용하던 컴퓨터는 32bit 컴퓨터이기 때문에 포인터 변수의 크기가 4바이트인 것을 확인할 수가 있다
포인터 변수는 포인터가 가리키는 변수의 형태가 같을 경우에만 대입 가능한데, 형 변환을 사용한 포인터 대입까지는 허용한다
int a = 10;
int *pi = &a;
double *pd;
pd = pi;
printf("%lf\n", *pd); // -925595921174321080000000000000.000000
컴파일러에 따라 결과가 다르게 나타나지만, 컴파일이 가능하다면 6번 라인처럼 이상한 값이 대입된다
double d = 3.4;
double *pd = &d;
int *pi;
pi = (int *)pd; // 형 변환
이 코드 역시 컴파일이 된다면, *pi를 출력했을 때 이상한 값이 출력되는데,

그림과 같이 포인터로 메모리를 직접적으로 쪼개 사용하기 때문에 정상적인 값을 출력할 수 없게 된다
Call by referencce & Call by value
그럼 이러한 포인터를 왜 배워야 하는가?
처음에 말했다시피 주소를 통해 변수를 참조하고 변경할 수 있기 때문이다
두 변수의 값을 교환하는 함수를 사용한다고 가정하자
함수는 실행되면 메모리에 해당 함수를 위한 임시 공간이 생성되게 되는데, 함수에 매개 변수로 일반 변수를 대입하게 되면, 임시 공간에서만 사용 가능한 지역 변수로 선언되어 함수가 종료되면 사용한 데이터가 휘발되어 버린다
그래서 매개 변수로 포인터 변수를 대입하는 것이다
이렇게 하게 되면 임시 공간에 생성된 데이터를 사용하는 것이 아니라, 함수 밖에서 선언된 데이터에 직접적으로 접근하기 떄문에 함수가 종료되더라도 함수 내에서 변경된 값을 그대로 유지할 수 있게 된다
Call by reference (참조에 의한 호출)은 앞서 말한 매개 변수로 포인터 변수를 주는 경우를 말하고, Call by value (값에 의한 호출)은 일반 변수를 매개 변수로 주는 경우를 말한다
#include <stdio.h>
void swap_reference(int *pa, int *pb);
void swap_value(int x, int y)
int main(void)
{
int a = 10, b = 20;
swap_value(a, b);
printf("a : %d, b : %d\n", a, b); // a : 10, b : 20
swap_reference(&a, &b); // 변수의 주소도 대입 가능
printf("a : %d, b : %d\n", a, b); // a : 20, b : 10
return 0;
}
void swap_reference(int *pa, int *pb) // Call by reference
{
int temp;
temp = *pa;
*pa = *pb;
*pb = temp;
return;
}
void swap_value(int x, int y) // Call by value
{
int temp;
temp = x;
x = y;
y = temp;
return;
}
배열과 포인터
배열의 이름은 주소와 같다
주소이기 때문에 동일한 타입의 포인터 변수에 저장할 수 있다
또한 주소는 그냥 정수가 아니라 자료형에 관한 정보를 가지는 특별한 값이기 때문에, 몇 가지 정해진 연산이 가능하고 연산의 결과는 해당 주소의 기본 타입 크기만큼 연산해주면 된다
int *pi;
double *pd;
printf("%d\n", pi); // 4194432
pi += 1;
printf("%d\n", pi); // 4194436
printf("%d\n", pd); // 2408448
pd += 1;
printf("%d\n", pd); // 2408456
이러한 성질 때문에 주소를 통해 배열을 참조할 수 있는데, 주소를 통해 가능하기 때문에 포인터를 통해서도 가능하게 된다
몇 가지 예시를 보면서 배열과 포인터간의 관계를 알아보자
int array[2];
int *pi;
*array = 10; // a[0] = 10;
*(array + 1) = 20; // a[1] = 20;
pi = array; // 배열의 이름은 주소
*pi = 30; // a[0] = 30;
*(pi + 1) = 40; // a[1] = 40;
for (int i = 0; i < 2; i++)
{
printf("%d ", *(pi + i));
}
printf("\n"); // 30 40
for (int i = 0; i < 2; i++)
{
printf("%d ", pi[i]);
}
printf("\n"); // 30 40
위와 같이 배열은 포인터로 표현이 가능하며, 연습을 통해 익숙해지도록 하자
배열 이름과 포인터간의 차이점도 존재한다
첫번재로 포인터에 어떠한 값을 연산하여 다시 포인터에 저장할 수 있으나, 배열명에 어떠한 값을 연산하여 다시 배열명에 저장하는 것은 불가능 한데, 이러한 이유 때문에 포인터에 증감 연산자 (++, --)를 사용할 수 있는 것이다
포인터에 간접 참조 연산자와 증감 연산자를 사용할 때, 증감 연산자를 전위 표현으로 사용하면 의도하지 않은 결과가 출력된다
그렇기 때문에 이런 경우 후위 표현을 사용해야하고, *(p++) 표현은 C언어의 연산자 우선순위에 따라 증감 연산자가 먼저 실행되기 때문에 *p++ 처럼 괄호를 생략해도 상관 없다
함수로 배열을 처리하기 위해서는 반드시 포인터가 필요하다
배열명은 첫 번째 배열 요소의 주소이기 때문에 해당 주소 값을 매개 변수로 주면, 함수는 주소 연산을 통해 모든 배열 요소를 사용할 수 있다
주의할 점은 함수 내부에서는 매개 변수로 입력된 배열의 크기를 알고 싶어도 알 수 없기 때문에, 함수 호출 이전에 크기를 구해 매개 변수로 함께 주도록 하자