2014. 5. 12. 16:33

참조(&) 연산자에 대해 궁금한 것들

때때로 다음과 같은 구문을 발견할 때가 있다.

 

    $myObj =& new myClass();

 

클래스의 인스턴스를 새로 생성하는 구문이다. 여기서 & 는 참조(reference) 연산자로, 유닉스 파일시스템의 하드링크(hardlink)와 같은 의미라고 한다. 즉, '가리키는 역할'이다. 하지만, C언어의 포인터(pointer)와는 다른데, 그 이유는 PHP에서는 모든 변수가 다 동일하게 '참조방식'이기 때문이다. 하나의 변수가 있고 다른 변수가 그것을 가르키는 것이 아니라, 모든 변수가 동등하게(?) 하나의 내용을 가르키는 것이라고 한다. 변수 이름과 변수 내용은 서로 구별되고, 하나의 변수 내용이 서로 다른 여러 개의 변수 이름을 가질 수 있다. (하지만, 실제 사용하는 데 있어서는 C언어의 포인터와 큰 차이가 없어 보인다. 그냥 개념상 그렇다는 얘기다.)

 

함수의 매개변수로 넘겨주기

 

이것이 유용하게 사용될 때에는 역시, 함수의 매개변수로 넘겨줄 때이다. 거의 C언어의 '포인터'와 유사하게 사용할 수 있다. PHP 역시 C언어와 동일하게 기본적으로 서브루틴 호출 스택이 'call by value' 방식이기 때문이다. & 연산자를 사용해서 '명시적'으로 call by reference 임을 함수의 프로토타입에 적시해주어야 한다. 대신, 호출시에는 지정해줄 필요가 없다. 문법은 다음과 같다:

 

<?php

function foo( $var )
{
  $var++;
}

function foo_ref( &$var )
{
  $var++;
}

$a = 5;
foo($a);
var_dump($a);
// $a is still 5


foo_ref($a);
var_dump($a);

// $a is 6 here
?>

 

 

위에서 보듯이, 변수 $a는 함수 foo()와 foo_ref() 내부에서 모두 값이 변경되었는데, 

foo()는 '복사'값을 받도록 되어 있고, 

foo_ref()는 '참조'를 매개변수로 받도록 되어 있기 때문에 foo_ref() 함수 호출이 완료된 후에도 $a 변수에는 함수 내부에서 변경된 값이 그대로 남아있다.

 

함수의 반환값으로 '참조'를 돌려받기

 

'참조(reference)는 어떤 함수내에서 사용되던 글로벌 변수이거나 스태틱(정적) 변수를 복사본이 아니라 그 자체를 밖으로 반환해줄 때에도 사용할 수 있다. 아래 예는 참조를 반환하도록 설정된 함수를 호출하는 방식의 차이에 따라 그 결과가 달라지는 것을 보여준다.

 

<?php

function &ref_return(&$arg) 

  $arg = $arg + 2; 
  return $arg; 
}

 

print '$r1 = & ref_return($arg);' . "\n"; 
$arg = 1; // initialize
printf("\$arg = %d\n", $arg); 
$r1 = & ref_return($arg); 
printf("\$r1 = %d\n", $r1); 
$r1 = $r1 + 1; 
printf("\$r1+1 = %d\n", $r1); 
printf("\$arg = %d\n", $arg); 

print '$r2 = ref_return($arg);' . "\n"; 
$arg = 1; // initialize
printf("\$arg = %d\n", $arg); 
$r2 = ref_return($arg); 
printf("\$r2 = %d\n", $r2); 
$r2 = $r2 + 1; 
printf("\$r2+1 = %d\n", $r2); 
printf("\$arg = %d\n", $arg);

?>

 

실행 결과는 아래와 같다.

 

$r1 = & ref_return($arg); 
$arg = 1 
$r1 = 3 
$r1+1 = 4 
$arg = 4 

$r2 = ref_return($arg); 
$arg = 1 
$r2 = 3 
$r2+1 = 4 
$arg = 3

 

$r1 은 참조 기호(&)를 붙여서 함수 호출을 한 경우이고, 

$r2 는 참조 기호(&) 없이 호출한 경우이다. 

참조 기호(&)를 붙여서 호출한 경우에는 함수 ref_return()의 반환값이 '참조'로 되돌려 지므로

$r1 에 +1 을 했을 때 원래의 변수인 $arg 역시 그 값이 +1 증가한 것을 알 수 있다.

 

반면, $r2 의 경우에는 +1 을 했을 때 $r2 자신만 값이 증가했고 $arg 의 값은 함수 내부에서 증가한 값(+2)에 그대로 머물러 있는 것을 알 수 있다. 

즉, $r2 는 '참조'가 반환된 것이 아니라 '복사'값이 반환되었다는 것을 확인할 수 있다.

 

그러므로, '참조'를 반환하도록 하기 위해서는 함수 설정에서도 & 기호를 사용해야 하고, 함수 호출시에도 반드시 & 기호를 사용해야만 한다. (두번 사용!)

 

조금 다른 경우를 살펴보자. 함수 설정에서는 '참조'가 아닌 '복사'를 반환하도록 되어 있는데, 함수 호출시에 '&' 기호를 붙이는 경우에는 어떻게 될까?

 

<?php

function ref_return2(&$arg) 

  $arg = $arg + 2; 
  return $arg; 
}

 

print '$r1 = & ref_return2($arg);' . "\n"; 
$arg = 1; // initialize
printf("\$arg = %d\n", $arg); 
$r1 = & ref_return2($arg); 
printf("\$r1 = %d\n", $r1); 
$r1 = $r1 + 1; 
printf("\$r1+1 = %d\n", $r1); 
printf("\$arg = %d\n", $arg); 

print '$r2 = ref_return2($arg);' . "\n"; 
$arg = 1; // initialize
printf("\$arg = %d\n", $arg); 
$r2 = ref_return2($arg); 
printf("\$r2 = %d\n", $r2); 
$r2 = $r2 + 1; 
printf("\$r2+1 = %d\n", $r2); 
printf("\$arg = %d\n", $arg);

?>

 

실행결과는 아래와 같다.

 

$r1 = & ref_return2($arg); 
$arg = 1 
$r1 = 3 
$r1+1 = 4 
$arg = 3 

$r2 = ref_return2($arg); 
$arg = 1 
$r2 = 3 
$r2+1 = 4 
$arg = 3

 

역시 예상했던 대로, 함수의 프로토타입 설정시에 참조 기호(&)를 생략하게 되면, 아무리 호출시에 참조 기호(&)를 덧붙인다고 해도 그 결과는 '참조'가 아닌 '복사'값이 반환된다. 

위의 예에서 $r1의 값은 +1 한 이후에 4가 되지만, 

원래의 변수인 $arg 의 값은 그대로 3으로 남아있다. 

즉, $r1 은 $arg 의 '참조'가 아닌 것이다. $r2 역시 마찬가지다.

 

% 주의사항 %

'참조'를 반환할 때 언뜻 생각하기에는 return &$arg; 와 같이 참조 기호(&)를 사용해야 할 것 같지만 그럴 필요가 없다. 함수의 프로토타입(prototype)에서 function &foo_ref() 와 같이 정의하는 것으로 끝이다. 오히려 return &$arg; 라고 써주면, Parse error 가 발생한다.

 


또다른 질문 하나. 스태틱(정적) 변수가 아닌 함수 내부의 변수인 경우 그것을 참조 반환 방식으로 외부로 끌어냈을 때 PHP는 어떻게 처리할까?

 

통상 함수 내부의 자동(auto) 변수는 스택(Stack)에 생성되므로 함수 호출이 완료되면 메모리 저장 장소 자체가 사라지므로 변수 자체가 없어진다. 그래서 volatile variable (휘발성 변수) 라고도 부른다.[반대말은 static variable.]

 

일단, 결론부터 말하면, PHP에서는 함수 내부의 변수를 참조 반환 방식으로 던져주면 '참조' 대신 '복사'로 처리한다. 원칙적으로는 휘발성 내부 변수가 외부로 노출되면 NULL로 초기화 되어야 하지만, PHP에서는 그냥 새로운 변수 이름에 새로운 변수 내용을 구성하는 것으로 대체해버린다. 이것을 어떻게 알 수 있냐고? 그냥 추측이다. 사실, 휘발성 변수는 함수 외부에서 재구성이 불가능하다. 새로 함수를 호출하면 그때는 아까와는 또다른 새로운 상황인 것이다. 비교할 대상 자체가 없기 때문에 비교 자체가 불가능하고 무의미하다.

 

 

'참조'로 객체 생성하기

 

그런데, 여기서 의문점 하나? 

새로운 객체 인스턴스를 생성하기 위한 연산자인 new 와 참조 연산자 & 가 함께 사용되어야만 하는 경우는

어떤 때일까?

 

대개의 경우 new 연산자에 의해 생성되는 객체 인스턴스는 그 시점에 최초로 생성된 것일 가능성이 높다. 

만일 그 전에 이미 생성된 적이 있었더라도 전혀 다른 인스턴스로 간주될 것이다. 

그런데도 굳이 & 연산자를 써서 생성한다는 건, 그 객체 클래스의 생성자(contructor) 함수 자체가 참조를 반환해주도록 한다는 의미일 것이다. 


왜 복사본이 아닌 참조를 반환해주려고 할까? 

반복적인 객체 인스턴스 생성과정 중에 뭔가 보존하거나 제어하고 싶은 게 있기 때문일 것이다. 

예를 들면, 

싱글톤(single-tone) 객체로 유일무이하게 사용해야 한다던가, 

객체 풀링(Object Pooling)을 통해서 생성되는 객체의 갯수를 적정 규모로 제어해야 한다던가 하는 

모종의 계획을 가지고 있는 경우일 것이다.

 

다시 의문점 하나? 만일 객체 생성자가 참조를 반환하도록 설정되어 있지 않은데, =& new 방식을 사용하면 어떤 일이 생길까? 에러가 생기지는 않는다. 그냥 기대하는 '참조' 대신 '복사'값이 반환될 것이다.

 

어떤 변수가 '참조'인지 아닌지 판단하는 방법?

 

PHP에서는 모든 변수 이름이 '참조'라고 한다. 그러므로, C언어에서 처럼 기억장소의 주소(어드레스)를 가지고 판단할 수는 없다. PHP에서 어떤 변수가 다른 변수의 '참조'인지 아닌지를 판단하기 위해서는 어떻게 해야 할까?

 

변수A가 변수B의 '참조'이기 위해서는 다음 세 가지 조건이 같아야 한다.

1) 변수 타입이 같다. 예) 문자열, 정수, 부동소숫점.

2) 변수 값이 같다.

3) 변수 내용의 저장장소가 같다.

 

조건 1)과 2)는 쉽게 판단할 수 있다. 비교 연산자 (===)를 사용하면 된다. 변수 값과 타입을 한번에 비교해준다.

 

조건 3)은 어떻게 하면 판단할 수 있을까?

 

가장 쉬운 방법은 변수A의 값을 살짝 바꿔놓고 변수B와 비교해보는 것이다. 만약 그렇게 했는데도 같다면 변수A는 변수B의 '참조'이다. 만약 다르다면, 둘은 각자 동일한 값을 가지는 별개의 독립적인 변수들이다.

 

이것을 한방에 해결해주는 PHP 내부의 연산자나 함수는 없을까?

 

--> 따로 이걸 처리해주는 연산자나 내부 함수는 없는 것 같다. 대신, 꽤 복잡하긴 하지만 함수로 만들어놓은 예제가 아래 링크에 있다.

 

http://php.justdn.org/manual/kr/language.references.spot.php  User Contributed Notes 란에 "BenBE at omorphia dot de"이 작성한 함수 is_ref()가 있다. 검사 방법은 위에서 말한 것과 동일하다. 즉, 하나를 변경했을 때 다른 하나가 함께 바뀌는지를 검사한다. 함수가 다소 복잡해지는 이유는 배열과 객체, 스칼라 등등 데이터 타입에 따라 조금씩 다르게 검사해야 하기 때문이다.

 

아무튼 실제로 is_ref()와 같은 함수를 사용해야 하는 경우는 '디버깅'해야 하는 때를 제외하고는 거의 없을 것 같다.