대답은 귀하의 관점에 따라 다릅니다.
C ++ 표준으로 판단하면 먼저 정의되지 않은 동작이 발생하므로 null 참조를 얻을 수 없습니다. 정의되지 않은 동작이 처음 발생한 후 표준은 모든 일이 발생하도록 허용합니다. 따라서을 작성 *(int*)0
하면 언어 표준 관점에서 널 포인터를 역 참조하는 그대로 정의되지 않은 동작이 이미 있습니다. 프로그램의 나머지 부분은 무관합니다. 일단이 표현식이 실행되면 게임에서 벗어나게됩니다.
그러나 실제로는 null 포인터에서 null 참조를 쉽게 만들 수 있으며 실제로 null 참조 뒤에있는 값에 액세스하려고 할 때까지 알 수 없습니다. 좋은 최적화 컴파일러는 정의되지 않은 동작을 확인하고 이에 의존하는 모든 것을 최적화하기 때문에 예제가 너무 간단 할 수 있습니다 (널 참조는 생성되지 않고 최적화됩니다).
그러나 이러한 최적화는 컴파일러가 정의되지 않은 동작을 증명하는 데 달려 있으며 이는 불가능할 수 있습니다. 파일 내에서 다음과 같은 간단한 함수를 고려하십시오 converter.cpp
.
int& toReference(int* pointer) {
return *pointer;
}
컴파일러는이 함수를 볼 때 포인터가 널 포인터인지 여부를 알지 못합니다. 따라서 포인터를 해당 참조로 바꾸는 코드를 생성합니다. (Btw : 포인터와 참조가 어셈블러에서 똑같은 짐승이기 때문에 이것은 noop입니다.) 이제 user.cpp
코드 가있는 다른 파일이 있다면
#include "converter.h"
void foo() {
int& nullRef = toReference(nullptr);
cout << nullRef; //crash happens here
}
컴파일러는 toReference()
전달 된 포인터를 역 참조 할 것인지 알지 못하며 실제로는 null 참조가 될 유효한 참조를 반환한다고 가정합니다. 호출은 성공하지만 참조를 사용하려고하면 프로그램이 충돌합니다. 바라건대. 표준은 분홍 코끼리의 모습을 포함하여 모든 일이 일어날 수 있도록 허용합니다.
이것이 왜 관련이 있는지 물어볼 수 있습니다. 결국 정의되지 않은 동작이 이미 내부에서 트리거되었습니다 toReference()
. 대답은 디버깅입니다. Null 참조는 null 포인터처럼 전파되고 확산 될 수 있습니다. 널 참조가 존재할 수 있다는 것을 모르고 생성을 피하는 방법을 배우면, 단순한 이전 int
멤버 를 읽으려고 할 때 멤버 함수가 충돌하는 이유를 파악하는 데 상당한 시간을 소비 할 수 있습니다 (답 : 인스턴스 멤버 호출에서 null 참조가 있었으므로 this
null 포인터도 마찬가지 이며 멤버는 주소 8로 위치하도록 계산됩니다.
그렇다면 null 참조를 확인하는 것은 어떻습니까? 당신은 줄을 주었다
if( & nullReference == 0 ) // null reference
귀하의 질문에. 음, 작동하지 않습니다. 표준에 따르면 널 포인터를 역 참조하면 정의되지 않은 동작이 있고, 널 포인터를 역 참조하지 않고는 널 참조를 만들 수 없으므로 널 참조는 정의되지 않은 동작의 영역 내에 만 존재합니다. 컴파일러는 정의되지 않은 동작을 트리거하지 않는다고 가정 할 수 있으므로 null 참조와 같은 것이 없다고 가정 할 수 있습니다 ( Null 참조 를 생성하는 코드를 쉽게 생성하더라도!). 따라서 if()
조건을 확인하고 사실 일 수 없다고 결론을 내리고 전체 if()
진술을 버립니다 . 링크 시간 최적화가 도입됨에 따라 강력한 방식으로 null 참조를 확인하는 것이 불가능 해졌습니다.
TL; DR :
Null 참조는 다소 무시 무시한 존재입니다.
그들의 존재는 불가능 해 보이지만 (= 표준에 의해)
존재하지만 (= 생성 된 기계 코드에 의해)
존재하는지 볼 수는 없지만 (= 당신의 시도는 최적화 될 것입니다),
어쨌든 그들은 당신을 모르게 죽일 수 있습니다 (= 프로그램이 이상한 지점에서 충돌하거나 더 나쁜 경우).
당신의 유일한 희망은 그것들이 존재하지 않는다는 것입니다 (= 그것들을 만들지 않도록 프로그램을 작성하십시오).
나는 그것이 당신을 괴롭히지 않기를 바랍니다!