C

[내배캠자습]C언어 챕터 7-2 : 주소 연산자와 역참조 연산자

BreadMushroom 2026. 3. 29. 15:05

지난 시간에 메모리 주소를 저장하는 방법에 대해 배웠습니다.

그럼 메모리 주소는 어떻게 얻을까요?

주소 연산자(address-of operator) &

피연산자의 메모리 주소를 반환하는 연산자. 기호 &(Ampersand)를 사용합니다.

사실 scanf() 함수에서 사용 했었던 주소 연산자

“~에”라고 해석하자고 배웠었습니다. 사실은 변수 앞에 &를 붙혀서 그 변수의 메모리 주소를 얻은 것입니다. 해당 메모리 주소에 입력 받은 값을 저장할 수 있게된 것입니다.

 

메모리 주소를 얻을 수 있는 두 가지 방법

1️⃣

주소 연산자

2️⃣

배열의 이름

int Array[1024];
	// 여기서 Array는 메모리 주소를 저장한 변수. 특히 그 메모리 주소는 배열의 시작 메모리 주소.

 

메모리 주소는 어떻게 얻는지 배웠습니다.

해당 메모리 주소에 저장 되어 있는 값을 알고 싶다면 어떻게 해야할까요?

역참조 연산자(indirection operator) *

피연산자로 포인터를 받아서, 해당 메모리 주소에 저장된 값을 읽거나 값을 수정할 때 사용하는 연산자.

[좋은습관] “메모리 그리기”

포인터 관련 예제를 풀 때는 “메모리 그리기”를 해봅시다. 사각형을 그린 다음, 변수들을 적고 각 변수에 메모리 주소를 0x100번지부터 적어줍니다. 그 상태로 한 줄씩 소스코드를 분석해봅시다. “천천히 읽기” + “메모리 그리기”

Ex070201) 주소 연산자와 역참조 연산자 1 [중요 샘플 코드]

아래 소스코드를 따라서 작성해봅시다. 따라 적은 코드의 출력 결과를 예측해보고, 예측 결과와 실행 결과를 비교해봅시다.

// Main.c

#include <stdio.h>

int main(void)
{
	int X = 10;
	int* PtrToX = &X;

	printf("%d\\n", *PtrToX);

	*PtrToX = 100;

	printf("%d\\n", *PtrToX);

	return 0;
}

// Main.c

#include <stdio.h>


int MyBank = 0;

void AddGold(int* PtrGold, int Gold)
{
	printf("사냥 정산\n");
	printf("어서오세요! 고블린 은행입니다~\n");
	printf("입금하실 금액을 접시위에 올려주세요!\n");
	
	*PtrGold = *PtrGold + Gold;
	printf("현재 잔액은 %d Gold 입니다.\n", *PtrGold);
}
int main(void)
{
	AddGold(&MyBank, 100);
	AddGold(&MyBank, 600);


	return 0;
}

Ex070202) 주소 연산자와 역참조 연산자 2

아래 소스코드를 따라서 작성해봅시다. 따라 적은 코드의 출력 결과를 예측해보고, 예측 결과와 실행 결과를 비교해봅시다.

// Main.c

#include <stdio.h>

int main(void)
{
	int X = 10;
	int* PtrToX = &X;

	printf("%d\\n", *PtrToX);

	*PtrToX = *PtrToX * 2;

	printf("%d\\n", *PtrToX);

	return 0;
}

Ex070203) 주소 연산자와 역참조 연산자 3

아래 소스코드를 따라서 작성해봅시다. 따라 적은 코드의 출력 결과를 예측해보고, 예측 결과와 실행 결과를 비교해봅시다.

// Main.c

#include <stdio.h>

int main(void)
{
	int X = 10, Y = 20;
	int* PtrToX = &X;

	printf("%d\\n", *PtrToX);

	*PtrToX = *PtrToX * 2;
	PtrToX = &Y;

	printf("%d\\n", *PtrToX);

	return 0;
}

Ex070204) 주소 연산자와 역참조 연산자 4

아래 소스코드를 따라서 작성해봅시다. 따라 적은 코드의 출력 결과를 예측해보고, 예측 결과와 실행 결과를 비교해봅시다.

[여기]에서 연산자 우선순위와 결합법칙 표를 볼 수 있습니다.

// Main.c

#include <stdio.h>

int main(void)
{
	int X = 10;
	int* PtrToX = &X;

	printf("%d\\n", X);
	printf("%d\\n", (*PtrToX)++);
	printf("%d\\n", X);
	printf("%d\\n", *PtrToX++); 
		// 문제 속의 문제. 계산될 연산자의 순서를 맞추시오.

	return 0;
}

포인터 Vs. 역참조 연산자 Vs. 곱셈 연산자

셋은 모두 *(Asterisk) 기호를 사용합니다. 포인터: 자료형 오른쪽에 붙습니다. 역참조 연산자: 피연산자가 한 개. 특히 피연산자로 메모리 주소를 받습니다. 곱셈 연산자: 피연산자가 두 개.

Ex070205) 주소 출력

메모리 주소를 출력할 때 %X로 출력하는 것은 올바르지 않습니다. 형식 지정자 %p로 메모리 주소를 출력하는 게 올바르나, void* 자료형으로 형변환이 필요합니다. 아래 소스코드를 따라서 작성 후 실행 해봅시다.

// Main.c

#include <stdio.h>

int main(void)
{
	int Num = 100;
	int* PointerToNum = &Num;

	printf("Num:               %d\\n", Num);
	printf("&Num:              %X\\n", &Num);
	printf("&Num:              %p\\n", (void*)&Num);
	printf("PointerToNum:      %X\\n", PointerToNum);
	printf("PointerToNum:      %p\\n", (void*)PointerToNum);
	printf("*PointerToNum:     %d\\n", *PointerToNum);
	printf("*PointerToNum:     %d\\n", *(&Num)); // 올바르진 않지만, 소거하는 식으로라도 이해해 보자.
	printf("(*PointerToNum)*2: %d\\n", (*PointerToNum) * 2);

	return 0;
}

 

포인터의 단점

큰 데이터들의 시작 메모리 주소만으로 가볍게 공유할 수 있다는 점은 좋습니다. 다만, 공유 받은 사람이 착한 사람인지는 모릅니다. 해당 주소로 가서 들어있는 값을 마음대로 수정 할수도 있습니다. 아주 강력한 기능임과 동시에 그만큼 잘못쓰면 큰일 날 수도 있습니다.

Ex070206) 다양한 자료형과 포인터

아래 소스코드를 따라서 작성해봅시다. 따라 적은 코드의 출력 결과를 예측해보고, 예측 결과와 실행 결과를 비교해봅시다.

// Main.c

#include <stdio.h>

int main(void)
{
	char Character = 'C';
	char* PtrToChar = &Character;
	short Num1 = 10;
	short* PtrToShort = &Num1;
	int Num2 = 11;
	int* PtrToInt = &Num2;
	float Num3 = 3.14f;
	float* PtrToFloat = &Num3;
	double Num4 = 3.141592;
	double* PtrToDouble = &Num4;

	printf("%p: %c(%d)\\n", (void*)PtrToChar, Character, Character);
	printf("%p: %d\\n", (void*)PtrToShort, Num1);
	printf("%p: %d\\n", (void*)PtrToInt, Num2);
	printf("%p: %f\\n", (void*)PtrToFloat, Num3);
	printf("%p: %f\\n", (void*)PtrToDouble, Num4);

	return 0;
}

 

Ex070207) char 자료형과 포인터

아래 소스코드를 따라서 작성해봅시다. 따라 적은 코드의 출력 결과를 예측해보고, 예측 결과와 실행 결과를 비교해봅시다.

// Main.c

#include <stdio.h>

int main(void)
{
	char Char1 = 'L';
	char Char2 = 'O';
	char* PtrToChar = &Char1;

	printf("%p: %c\\n", (void*)PtrToChar, *PtrToChar);

	PtrToChar = &Char2;
	printf("%p: %c\\n", (void*)PtrToChar, *PtrToChar);

	PtrToChar = &Char1;
	printf("%p: %c\\n", (void*)PtrToChar, *PtrToChar);

	return 0;
}

Ex070208) 뒤섞기 1

아래 소스코드를 따라서 작성해봅시다. 따라 적은 코드의 출력 결과를 예측해보고, 예측 결과와 실행 결과를 비교해봅시다.

// Main.c

#include <stdio.h>

int main(void)
{
	int Num1 = 10;
	int Num2 = 20;
	int Num3 = 30;

	int* Ptr1 = &Num1;
	int* Ptr2 = &Num2;
	int* Ptr3 = &Num3;

	Ptr3 = Ptr2;
	Ptr2 = Ptr1;
	Ptr1 = Ptr2;

	/* Q. num1을 가리키는 포인터는? */
	printf("%p: %d\\n", (void*)Ptr1, *Ptr1);
	printf("%p: %d\\n", (void*)Ptr2, *Ptr2);
	printf("%p: %d\\n", (void*)Ptr3, *Ptr3);

	return 0;
}

Ex070209) 뒤섞기 2

아래 소스코드를 따라서 작성해봅시다. 따라 적은 코드의 출력 결과를 예측해보고, 예측 결과와 실행 결과를 비교해봅시다.

// Main.c

#include <stdio.h>

int main(void)
{
    int Num1 = 15;
    int Num2 = 30;
    int Num3 = 45;

    int* Ptr1 = &Num1;
    int* Ptr2 = &Num2;
    int* Ptr3 = &Num3;

    Ptr1 = Ptr2;
    Ptr2 = Ptr3;

    *Ptr3 *= 2;
    *Ptr1 += *Ptr3;
    *Ptr2 *= 2;

    /* Q. 출력 결과는? */
    printf("%d %d %d", Num1, Num2, Num3);

    return 0;
}

참조에 의한 호출 Vs. 값에 의한 호출

원본값이 바뀌냐 Vs. 안 바뀌냐.

함수 A가 함수 B를 호출 할 때, 인자를 전달하면서 호출한다고 가정해봅시다. 함수 B가 종료될 때 인자의 원본값도 바뀐다면 참조에 의한 호출입니다. 인자의 원본값이 바뀔 수 없다면 값에 의한 호출이라고 부릅니다.

Ex070210) Swap() 함수 1

아래 소스코드를 따라서 작성해봅시다. 따라 적은 코드의 출력 결과를 예측해보고, 예측 결과와 실행 결과를 비교해봅시다.

// Main.c

#include <stdio.h>

void Swap1(int Op1, int Op2);

void Swap2(int* Op1, int* Op2);

int main(void)
{
	int Num1 = 10;
	int Num2 = 20;

	printf("before Swap1()\\n");
	printf("Num1: %d, Num2: %d\\n", Num1, Num2);

	Swap1(Num1, Num2);

	printf("after Swap1()\\n");
	printf("Num1: %d, Num2: %d\\n\\n", Num1, Num2);

	printf("before Swap2()\\n");
	printf("Num1: %d, Num2: %d\\n", Num1, Num2);

	Swap2(&Num1, &Num2);

	printf("after Swap2()\\n");
	printf("Num1: %d, Num2: %d\\n\\n", Num1, Num2);

	return 0;
}

void Swap1(int Op1, int Op2)
{
	int Temp;

	Temp = Op1;
	Op1 = Op2;
	Op2 = Temp;

	return;
}

void Swap2(int* Op1, int* Op2)
{
	int Temp;

	Temp = *Op1;
	*Op1 = *Op2;
	*Op2 = Temp;

	return;
}

Swap1 함수는 Num1과Num2의 값을 복사해서 계산하기 때문에
인자의 원본값이 바뀌지않고 함수가 동작을 마치면 지역변수로서 사라진다.
Swap2 함수는 인자의 원본 주소값을 받아서 동작해서 
함수내에서 값이 바뀌면 함수가 종료돼도 그대로 원본에 영향을 미친다.
그래서 Swap1의 함수 동작 후 Num1, Num2 출력 결과는 함수동작 전과 동일하다.
반면에 Swap2의 함수 동작 후 Num1, Num2 출력 결과는 함수동작 전과 값이 서로 바뀌어 출력된다.

 

 

Ex070211) Swap() 함수 2

이전 예제에서 배운 Swap() 함수를 스스로 구현 후 호출 해 봅시다.

//입력1
3 2
//출력1
2 3
#include <stdio.h>

void Swap(int* c, int* d)
{
	int T;
	T = *c;
	*c = *d;
	*d = T;

	return;
}

int main(void)
{
	int a = 3;
	int b = 2;

	printf("before Swap: a:%d , b:%d \n", a, b);
	Swap(&a, &b);
	printf("Swap: a:%d , b:%d \n", a, b);
	return 0;
}

Ex070212) GetMinMax() 함수

아래 소스코드를 따라서 작성해봅시다. 따라 적은 코드의 출력 결과를 예측해보고, 예측 결과와 실행 결과를 비교해봅시다.

// Main.c

#include <stdio.h>
#include <assert.h>

void GetMinMax(const size_t InArrayLength, const int InArray[], int* OutPtrToMin, int* OutPtrToMax);

int main(void)
{
    const int Nums[5] = { 7, 8, 1, 10, 5 };
    int ResultMin;
    int ResultMax;

    GetMinMax(5, Nums, &ResultMin, &ResultMax);

    printf("ResultMin: %d\\n", ResultMin);
    printf("ResultMax: %d\\n", ResultMax);

    return 0;
}

void GetMinMax(const size_t InArrayLength, const int InArray[], int* OutPtrToMin, int* OutPtrToMax)
{
    size_t i;

    *OutPtrToMin = InArray[0];
    *OutPtrToMax = InArray[0];

    for (i = 0; i < InArrayLength; ++i)
    {
        if (InArray[i] < *OutPtrToMin)
        {
            *OutPtrToMin = InArray[i];
        }

        if (*OutPtrToMax < InArray[i])
        {
            *OutPtrToMax = InArray[i];
        }
    }

    return;
}