TIL PHP 4/20/2021

엽토군·2021년 4월 20일
0

TIL

목록 보기
2/13

PHP 배열 내부 포인터

PHP 배열에 포인터 개념이 있다고?!

PHP는 의도적으로 C* 언어의 고차원적 개념들을 숨기거나 걷어냈다. 포인터가 대표적인 사례. 사실은 존재하지만, 없는 것처럼 해놨다. PHP 배열이 거지 같다고 하는 것은 이런 부분에서 영향 받는 것도 있다.

회사 소스 읽어보다가 이런 부분 발견.

if (!empty($data)) return current($data);

current()라는 내장함수가 있어?? 하고 살펴보니 그거만 있는 게 아니다. 애초에 PHP 배열 포인터라는 개념을 모르는 한 이 내장함수들은 아무 의미가 없다. 여기서는 우선 current()를 중심으로 살펴보자.

모든 배열에는 내부 포인터가 있으며, 그 포인터는 "현재" 원소를 가리키게 되어 있고, 현재 원소란 배열에 처음으로 삽입된 원소를 말한다.

그 멘트 밑에 나와 있는 공식문서의 예제 코드가 실로 쇼킹이다.

$transport = array('foot', 'bike', 'car', 'plane');
$mode = current($transport); // $mode = 'foot';
$mode = next($transport);    // $mode = 'bike';
$mode = current($transport); // $mode = 'bike';
$mode = prev($transport);    // $mode = 'foot';
$mode = end($transport);     // $mode = 'plane';
$mode = current($transport); // $mode = 'plane';

PHP 배우면서 웬만한 거에는 안 놀라게 되었다고 생각했는데 와… 이건 충격적이다. 정말 PHP에 안 되는 건 없구나!!

'근데… 이런 게 왜 필요하지??' 하는 생각을 하며 스크롤을 내리고 있자니까 그러면 그렇지 PHP 공식사이트답게 사용자 노트에 바로 이 질문의 답이 되는 메모가 이미 올라와 있는 것이 눈에 띈다.

크기가 큰 배열(내가 해본 바로는 원소 8만 개 이상)의 경우, 배열을 순서대로 처리할 때 인덱스 $a[$i]를 사용하면 효율이 떨어질 (느려질) 수 있어서 부득이 current($a) 사용해야 했음.

읽고 나서 가만 생각해 보니 당연한 얘기다. 자료가 크면 순회가 어렵고, 그때 포인터 개념은 도움이 될 수 있다. 그런데 사실은 순서가 그 반대다. 즉, 포인터가 먼저 있었고, 포인터를 숨기는 문법과 언어와 컨벤션이 제안되었으되, 그것들로 처리를 못 하는 특별한(ex. 무거운, 까다로운) 케이스에 대해서는 다시 포인터를 써야 하는 것이다.

포인터를 쓸 일이 있긴 있어!?

말이 이상한데 아래 연구사례를 보면 명확하다. 다음 코드는 무엇을 덤프하게 될까? (단 여기서 key()current($var)가 반환하는 value에 엮인 key를 돌려주는 함수)

$var = ['a' => '4', 'b' => '7'];
foreach ($var as $value) { /* 어쩌구 저쩌구... */ }
var_dump(key($var));

PHP 5에서는 NULL이 찍혔다고 한다.

  1. $var에 대해서 foreach를 돌기 위해 내부 포인터가 사용된다.
  2. 그 포인터가 'b' => '7'을 처리하고 지나간다.
  3. 제 3행에서 그 포인터는 마지막 원소 그 다음을 가리키고 있으므로, current($var) 찍어보면 false가 나온다.
  4. 그래서 NULL이 찍힌다.

그런데 PHP 7에서는 "a"가 나온다고 한다.

PHP가 7로 올라가면서부터는 foreach가 배열 내부 포인터를 조작하지 않게 됐다고 한다. 위에서 설명한 것처럼 '내부 포인터가 사용'되는 방식으로 작동하지 않는다는 것이다. 그래서, PHP 5 시절에 배열의 원소를 넣거나 빼거나 하는 작업을 할 때 레퍼런스를 사용하던 관행이 다소 정정되어야 했다는 모양이다.

이를테면 이런 식이다.

$array = [0, 1, 2];
foreach ($array as &$val) {
    var_dump(current($array));
    $array[2] = 200;
}

위 코드는 $array의 각 원소를 명시적으로 참조하면서 foreach 순회를 하는데, PHP 5에서 위 코드가 찍는 것은 다음과 같다.

int(1)
int(200)
bool(false)

지금 순회 중인 배열의 원소들을 명시적으로 참조하고 있기 때문에 루프 안에서 마음대로 조작 가능해지면서 난리굿판이 벌어진다. $array[2] = 200;이니까 망정이지 만약에 저기에 비즈니스 로직 들어가고 $array가 사용자가 임의 정의 가능한 배열이 되고 하면… 헬이 열리는 것이다.

PHP 7에서는 그런 거 없다.

int(0)
int(0)
int(0)

꼬우면 foreach ($a as $k => $v) 형태로 돌면 된다.

마치며

차라리 몰랐을땐 맘편하게 foreach 돌리기라도 했지 이젠 영 찝찝해서 못쓰겠다 ㅜ.ㅜ PHP 소스를 뒤져서 foreach가 실제 작동하는 원리를 알아내지라도 않는 이상…

profile
4년차 PHP 개발자입니다.

0개의 댓글