C언어 study_포인터

민성철·2022년 11월 15일
0

nadocoding_study_C

목록 보기
12/17

포인터

변수의 주소와 값 알아내고 변경

변수를 선언하면 메모리 공간의 값이 저장됩니다.
선언한 변수의 주소(메모리 위치)를 알기 위해선 변수명 앞에 &를 붙히면 알 수 있습니다.
그리고 변수의 위치를 출력하기 위해서는 서식 지정자에 %p를 사용합니다.
그러면 메모리 주소를 16진수 형태로 출력합니다.

#include <stdio.h>

int main(void) {
	int ironman = 1;
    printf("address : %p\n", &ironman);
    return 0;
}

output

address : 0x16b6b3310

즉, output의 결과 값이 ironman의 주소이고, 이 값은 변수 ironman이 메모리 어느 위치에 있는지를 나타냅니다.
변수의 주소는 컴퓨터마다 다르고, 심지어 같은 컴퓨터라도 실행할 때마다 다르게 나올 수 있습니다.

포인터 변수를 선언할 때는 변수명 앞에 '*'을 넣어 줍니다.
포인터 변수는 메모리의 주소값을 저장하는 변수입니다.

포인터 변수는 일반 변수의 주소값을 저장할 수 있고, 때문에 일반 변수의 값을 출력 할 수도 있습니다.
또한, 일반 변수의 값이 저장되어 있는 주소를 알고 있기 때문에 값을 변경할 수도 있습니다.
추가로 일반 변수의 주소값이 지정된 포인터 변수의 값을 출력하기 위해서는 *을 넣어줍니다.

포인터의 설명을 돕기 위해, 아래 예시 코드로 보겠습니다.

#include <stdio.h>

int main(void) {
	int ironman = 1;
    int * pointerman;
    pointerman = &ironman;
    printf("ironman address : %p \nanswer : %d\n", &ironman, iornman);
    printf("pointerman address : %p \nanswer : %d\n", pointerman, *pointerman);
    *pointerman = 4;
    printf("ironman address : %p \nchange answer : %d\n", &ironman, ironman);
    return 0;
}

output

ironman address : 0x16b6b3310
answer : 1
pointerman address : 0x16b6b3310
answer : 1
ironman address : 0x16b6b3310
change answer : 4

포인터

포인터 추가

포인터를 추가해보겠습니다.

int* spy = pointerman;

이제 포인터 변수 2개는 같은 주소, 즉 하나의 메모리 공간을 가리킬 수 있습니다.
이는 포인터 변수 2개 이상으로 어떻게 활용할 수 있는지를 볼 수 있습니다.
아래 예시 코드를 보겠습니다.

#include <stdio.h>

int main(void) {
	int ironman = 1;
    int * pointerman;
    pointerman = &ironman;
    printf("ironman address : %p \nanswer : %d\n", &ironman, iornman);
    printf("pointerman address : %p \nanswer : %d\n", pointerman, *pointerman);
    *pointerman = 4;
    printf("ironman address : %p \nchange answer : %d\n", &ironman, ironman);
    int* spy = pointerman;
    *spy = *spy - 3;
    printf("spy address : %p \nchange answer : %d\n", spy, ironman);
    return 0;
}

output

ironman address : 0x16b6b3310
answer : 1
pointerman address : 0x16b6b3310
answer : 1
ironman address : 0x16b6b3310
change answer : 4
spy address : 0x16b6b3310
change answer : 1

참고로 주소값은 같기 때문에 2번째, 3번째 포인터 변수의 주소값은 최근 포인터 변수값을 줘도 되고, 일반 변수의 주소값을 줘도 무방합니다.

포인터

포인터로 배열 다루기

포인터 변수는 일반 변수의 주소값을 찾을 수 있고, 일반 변수의 값을 출력하거나 바꿀수 있었습니다.
여기서 배열을 바꾼다고 생각해봤을때, 배열은 일반 변수와 다르게 연속된 공간을 가지고 있어, 어떻게 접근해야 될지 생각이 많아질 겁니다.
하지만, 배열의 첫 번째 즉, arr[0]의 주소값으로 포인터 변수에 저장하여 접근하면 됩니다.
그럼 배열도 일반 변수처럼 주소와 각 요소의 값을 출력하거나 변경할 수 있습니다.

여기서 주의할 점은, 배열의 요소에 포인터 변수를 지정할 수 없습니다.
또한, 배열의 요소를 가르킬 때 일반적으로 arr[i]로 작성했지만 다음과 같이 작성할 수도 있습니다.

// printf("배열 arr[%d]의 값 : %d\n", i, arr[i]);
printf("배열 arr[%d]의 값 : %d\n", i, *(arr + 1));

위 코드에서 arr은 arr 배열이 시작되는 주소, 즉 첫 번째 요소의 주소를 가지고 있습니다.
arr[i] 는 배열 첫 번째 요소의 주소로부터 i번째에 해당하는 요소의 값을 가져온다는 의미입니다.
따라서 두번째 코드처럼 *(arr + i) [별 : 주소의 값을 출력하는 부호, arr : 배열이 시작되는 주소, + i : 시작되는 주소로 부터 i 번째] 따라서 두 코드는 문법적으로 완전히 같은 문장입니다.
예시 코드는 아래와 같습니다.

// arr의 값은 arr 배열 첫 번째 요소의 주소와 같음
arr == &arr[0];
// arr에서 i번째 떨어진 곳에 저장된 값과 arr배열의 i 인덱스 값은 같음
*(arr + i) == arr[i];

arr 자체가 가지고 있는 주소값과 arr 배열 첫 번째 요소의 주소가 같음을 알 수 있음을 아래의 코드로 확인하겠습니다.

#include <stdio.h>

int main(void) {
	int arr[] = {100, 200, 300};
    printf("arr 자체의 값 : %p\n", arr);
    printf("arr[0]의 주소 : %p\n", &arr[0]);

output

arr 자체의 값 : 0x16dce7308
arr[0]의 주소 : 0x16dce7308
  • *와 &를 함께 사용할 때
    코드에서 &는 주소를 나타내고, 별(velog 서식상 앞뒤로 있으면 가운데 글자가 굵어져 표시하기 어려워 한글로 대체 함)은 주소의 값을 나타내기 때문에 둘은 상쇄됩니다.
    그래서 별과 &을 같이 사용하게 되면 아무것도 붙이지 않고 사용하는 것 과 같은 효과를 냅니다.

더 나아가 포인터로 배열의 값을 바꿔줄 수 있습니다.
배열명은 배열의 시작 주소와 같다고 했습니다. 배열은 연속된 공간에 할당하므로 주소가 연속됩니다. 따라서, 배열의 시작 주소인 첫 번째 요소의 주소를 전달하면 시작 주소를 기준으로 지정한 요소의 위치를 찾아 값을 바꿀 수 있습니다.

#include <stdio.h>

int change_array(int* arr);

int main(void) {
	int arr[2] = {10, 20, 30};
    change_array(arr); // == change_array(&arr[0]);
    for (int i = 0; i < 3; i++) {
    	printf("%d\n", arr[i]);
    }
    return 0;
}

int change_array(int* arr) {
	arr[2] = 50; // 주소값을 받고 값을 변경했기 때문에 전달값이 없어도 됩니다.
}

output

10
20
50

포인터

함수에서 포인터 사용하기

이번에는 일반 변수 a, b를 각 10과 20으로 정의해준 뒤, 각 변수의 값을 변경해주는 함수를 활용하여 값과 주소값을 살펴보겠습니다.
아래 예시 코드를 먼저 적어보겠습니다.

#include <stdio.h>

void swap(int a, int b);
void swap_addr(int *a, int *b);

int main(void) {
	int a = 10;
    int b = 20;
    printf("함수 전 a의 값 : %d ,주소 : %p\n", a, &a);
    printf("함수 전 b의 값 : %d ,주소 : %p\n", b, &b);
    swap(a, b);
    swap_addr(&a, &b);
    return 0;
}

void swap(int a, int b) {
	int temp = a;
    a = b;
    b = temp;
    printf("첫 번째 함수 a의 값 : %d ,주소 : %p\n", a, &a);
    printf("첫 번쨰 함수 b의 값 : %d ,주소 : %p\n", b, &b);
}

void swap_addr(int* a, int* b) {
	int* temp = a;
    a = b;
    b = temp;
    printf("두 번째 함수 a의 값 : %d ,주소 : %p\n", *a, a);
    printf("두 번째 함수 b의 값 : %d ,주소 : %p\n", *b, b);
}

output

함수 전 a의 값 : 10 ,주소 : 0x16d06f328
함수 전 b의 값 : 20 ,주소 : 0x16d06f324
첫 번째 함수 a의 값 : 20 ,주소 : 0x16d06f2dc
첫 번째 함수 b의 값 : 10 ,주소 : 0x16d06f2d8
두 번째 함수 a의 값 : 20 ,주소 : 0x16d06f324
두 번째 함수 b의 값 : 10 ,주소 : 0x16d06f328

여기서 알아야할 점은, 첫 번째 함수인 각 변수의 값을 변경해주는 함수 swap()에서 값이 잘 나왔지만, 주소는 main()함수와 다르다는걸 확인할 수 있습니다.
이말인 즉슨, 값은 전달값으로 받아 출력했지만, main()함수와 swap()함수에서 사용되는 각 변수의 저장하는곳(주소)은 다르다는 겁니다.
즉, swap() 함수 안에서 다른 공간에 변수 a가 새로 만들어 졌다는 뜻이며, 이처럼 함수를 호출하면서 전달값으로 변수를 넘기면 호출한 함수 안에서는 변수 자체가 아닌, 전달받은 변수의 값만 복사해서 사용하고, 이를 '값에 의한 호출(call by value)'이라 합니다.

그렇기 때문에 두번째 함수를 적용했을때는 전달값이 일반 변수가 아닌, 포인터 변수값을 전달받는다고 매개변수(parameter)를 선언 하였고, 그렇기 때문에 인수(agument)값으로 main()함수에서 선언된 a변수와 b변수의 주소값이 전달되어 동일한 것을 알 수 있습니다.
이처럼 함수를 호출하면서 전달값으로 변수의 주소를 넘기면 호출한 함수 안에서 변수의 주소를 참조해 값을 사용하거나, 수정할 수 있고, 이를 '참조에 의한 호출(call by reference)'이라 합니다.

profile
ENTJ-A

0개의 댓글