주소로 전달 (Pass by address)
함수에 변수를 전달할 수 있는 또 다른 방법이 있는데, 주소를 사용하는 것이다. 인수가 주소이기 때문에 함수 매개 변수는 포인터다. 함수는 가리키는 값에 접근하거나 변경하기 위해 포인터를 역참조할 수 있다.
다음은 주소로 전달된 매개 변수를 사용하는 함수의 예제다.
#include <iostream>
void foo(int* ptr)
{
*ptr = 6;
}
int main()
{
int value = 5;
std::cout << "value = " << value << '\n';
foo(&value);
std::cout << "value = " << value << '\n';
return 0;
}
/*
value = 5
value = 6
*/
보시다시피, 함수 foo()는 포인터 매개 변수 ptr을 통해 인수의 값을 변경한다.
주소로 전달(Pass by address)은 주로 배열과 함께 사용한다. 예를 들어, 다음 함수는 배열의 모든 값을 출력한다.
void printArray(int* array, int length)
{
for (int index=0; index < length; ++index)
std::cout << array[index] << ' ';
}
다음은 위 함수를 호출하는 예제 프로그램이다.
int main()
{
int array[6] = { 6, 5, 4, 3, 2, 1 }; // remember, arrays decay into pointers
printArray(array, 6); // so array evaluates to a pointer to the first element of the array here, no & needed
}
// 6 5 4 3 2 1
배열이 함수에 전달될 때 포인터로 변환되므로 길이를 별도의 매개 변수로 전달해야 한다는 것을 기억하자.
주소를 통해 전달된 매개 변수를 역참조하기 전에 null 포인터인지 항상 확인하는 게 좋다. null 포인터를 역참조하면 프로그램이 중단된다. 다음은 null 포인터 검사를 수행하는 printArray() 함수다.
void printArray(int* array, int length)
{
// if user passed in a null pointer for array, bail out early!
if (!array)
return;
for (int index=0; index < length; ++index)
cout << array[index] << ' ';
}
int main()
{
int array[6] = { 6, 5, 4, 3, 2, 1 };
printArray(array, 6);
}
const 주소로 전달 (Passing by const address)
위 예제에서 printArray()는 함수 내에서 인수를 수정하지 않기 때문에 배열 매개 변수를 const로 만드는 것이 좋다.
void printArray(const int* array, int length)
{
// if user passed in a null pointer for array, bail out early!
if (!array)
return;
for (int index=0; index < length; ++index)
std::cout << array[index] << ' ';
}
int main()
{
int array[6] = { 6, 5, 4, 3, 2, 1 };
printArray(array, 6);
}
이것은 printArray()는 전달된 배열 인수를 수정하지 않는다는 것을 한눈에 알 수 있게 해준다.
주소는 실제로 값에 의해 전달된다. (Addresses are actually passed by value)
함수에 포인터를 전달하면 포인터의 값(포인터가 가리키는 주소)이 인수에서 함수의 매개 변수로 복사된다. 다시 말해서, 값으로 전달되었다! 함수 매개 변수의 값을 변경하면 복사본만 변경된다. 따라서 원래 포인터 인수는 변경되지 않는다.
#include <iostream>
void setToNull(int* tempPtr)
{
// we're making tempPtr point at something else, not changing the value that tempPtr points to.
tempPtr = nullptr; // use 0 instead if not C++11
}
int main()
{
// First we set ptr to the address of five, which means *ptr = 5
int five = 5;
int* ptr = &five;
// This will print 5
std::cout << *ptr << std::endl;
// tempPtr will receive a copy of ptr
setToNull(ptr);
// ptr is still set to the address of five!
// This will print 5
if (ptr)
std::cout << *ptr;
else
std::cout << " ptr is null";
return 0;
}
// 5
// 5
매개 변수 tempPtr은 ptr이 보유하고 있는 주소의 복사본을 전달받는다. tempPtr을 다른 값(Ex. nullptr)을 가리키도록 변경하더라도 ptr이 가리키는 값은 변경되지 않는다.
주소 자체가 값에 의한 전달이 되더라도 여전히 그 주소를 역참조하여 인수의 값을 변경할 수 있다.
- 인수를 주소로 전달할 때 함수 매개 변수는 인수에서 주소의 복사본을 받는다. 이 시점에서 함수 매개 변수와 인수는 모두 같은 값을 가리킨다.
- 함수 매개 변수가 가리키는 값을 변경하기 위해 역참조 하면 함수 매개 변수와 인수가 모두 같은 값을 가리키기 때문에 인수가 가리키는 값에 영향을 준다.
- 함수 매개 변수가 다른 주소로 지정되면 함수 매개 변수가 복사본이기 때문에 인수에 영향을 미치지 않으므로 복사본을 변경해도 원본에 영향을 미치지 않는다. 함수 매개 변수의 주소를 변경한 후에는 함수 매개 변수와 인수가 다른 값을 가리키므로 역참조하더라도 더는 인수가 가리키는 값에 영향을 미치지 않는다.
#include <iostream>
void setToSix(int* tempPtr)
{
*tempPtr = 6; // we're changing the value that tempPtr (and ptr) points to
}
int main()
{
// First we set ptr to the address of five, which means *ptr = 5
int five = 5;
int* ptr = &five;
// This will print 5
std::cout << *ptr << std::endl;
// tempPtr will receive a copy of ptr
setToSix(ptr);
// tempPtr changed the value being pointed to to 6, so ptr is now pointing to the value 6
// This will print 6
if (ptr)
std::cout << *ptr;
else
std::cout << " ptr is null";
return 0;
}
// 5
// 6
참조로 주소 전달하기 (Passing addresses by reference)
그러면 "함수 내에서 인수가 가리키는 주소를 변경하려면 어떻게 해야 할까?" 라는 의문이 들 수 있다. 간단하게 참조로 주소를 전달해서 해결할 수 있다.
#include <iostream>
// tempPtr is now a reference to a pointer, so any changes made to tempPtr will change the argument as well!
void setToNull(int*& tempPtr)
{
tempPtr = nullptr; // use 0 instead if not C++11
}
int main()
{
// First we set ptr to the address of five, which means *ptr = 5
int five = 5;
int* ptr = &five;
// This will print 5
std::cout << *ptr << std::endl;
// tempPtr is set as a reference to ptr
setToNull(ptr);
// ptr has now been changed to nullptr!
if (ptr)
std::cout << *ptr;
else
std::cout << " ptr is null";
return 0;
}
// 5
// ptr is null
setToNull(ptr) 호출이 ptr의 값을 &five에서 nullptr로 실제로 변경했음을 보여준다.
주소로 전달의 장단점 (Pros and cons of pass by address)
주소로 전달의 장점:
- 주소로 전달은 함수가 인수 값을 변경할 수 있으므로 유용하다.
- 인수의 복사본이 만들어지지 않으므로 구조체 또는 클래스와 함께 사용하는 경우에 빠르다.
주소로 전달의 단점:
- 리터럴과 표현식은 주소가 없으므로 사용할 수 없다.
- 모든 값은 null인지 확인해야 한다. null 값을 역참조하려고 하면 충돌이 발생한다.
- 포인터 역참조는 값에 직접 접근하는 것보다 더 느리다.
주소로 전달을 사용해야 하는 경우:
주소로 전달을 사용하지 않아야 하는 경우:
- 구조체 또는 클래스를 전달할 때 (참조로 전달 사용)
- 기본 자료형의 값을 전달할 때 (값으로 전달 사용)
주소로 전달과 참조로 전달은 거의 같은 장단점이 있다. 그러나 참조로 전달하는 것이 주소로 전달하는 것보다 더 안전하므로 대부분의 경우 참조로 전달하는 게 좋다.
번역: 이 포스트의 원문은 http://www.learncpp.com/cpp-tutorial/74-passing-arguments-by-address/ 입니다.