etc./StackOverFlow

C++에서 포인터 변수와 참조 변수의 차이점은 무엇입니까?

청렴결백한 만능 재주꾼 2021. 10. 6. 03:01
반응형

질문자 :prakash


참조가 구문상의 설탕이라는 것을 알고 있으므로 코드를 읽고 쓰기가 더 쉽습니다.

그러나 포인터 변수와 참조 변수의 차이점은 무엇입니까?



  1. 포인터를 다시 할당할 수 있습니다.

     int x = 5; int y = 6; int *p; p = &x; p = &y; *p = 10; assert(x == 5); assert(y == 10);

    참조는 다시 바인딩할 수 없으며 초기화 시 바인딩해야 합니다.

     int x = 5; int y = 6; int &q; // error int &r = x;
  2. 포인터 변수에는 고유한 ID가 있습니다. 단항 & sizeof 연산자로 측정할 수 있는 특정 공간입니다. 참조에서 이러한 연산자를 사용하면 참조가 바인딩된 모든 항목에 해당하는 값이 반환됩니다. 참조 자체의 주소와 크기는 보이지 않습니다. 이러한 방식으로 참조는 원래 변수의 ID를 가정하므로 참조를 동일한 변수의 다른 이름으로 생각하는 것이 편리합니다.

     int x = 0; int &r = x; int *p = &x; int *p2 = &r; assert(p == p2); // &x == &r assert(&p != &p2);
  3. 추가 수준의 간접 참조를 제공하는 포인터에 대해 임의로 중첩된 포인터를 가질 수 있습니다. 참조는 한 수준의 간접 참조만 제공합니다.

     int x = 0; int y = 0; int *p = &x; int *q = &y; int **pp = &p; **pp = 2; pp = &q; // *pp is now q **pp = 4; assert(y == 4); assert(x == 2);
  4. nullptr 할당할 수 있지만 참조는 기존 개체에 바인딩해야 합니다. nullptr 대한 참조를 바인딩할 수 있지만 이는 정의되지 않고 일관되게 작동하지 않습니다.

     /* the code below is undefined; your compiler may optimise it * differently, emit warnings, or outright refuse to compile it */ int &r = *static_cast<int *>(nullptr); // prints "null" under GCC 10 std::cout << (&r != nullptr ? "not null" : "null") << std::endl; bool f(int &r) { return &r != nullptr; } // prints "not null" under GCC 10 std::cout << (f(*static_cast<int *>(nullptr)) ? "not null" : "null") << std::endl;

    nullptr 인 포인터에 대한 참조를 가질 수 있습니다.

  5. 포인터는 배열을 반복할 수 있습니다. ++ 를 사용하여 포인터가 가리키는 다음 항목 + 4 를 사용하여 5번째 요소로 이동할 수 있습니다. 포인터가 가리키는 개체의 크기는 중요하지 않습니다.

  6. 포인터가 가리키는 * 로 역참조해야 하지만 참조는 직접 사용할 수 있습니다. 클래스/구조체에 대한 포인터는 -> 를 사용하여 해당 멤버에 액세스하는 반면 참조는 . .

  7. 참조는 배열에 넣을 수 없지만 포인터는 (사용자 @litb가 언급함)

  8. Const 참조는 임시에 바인딩될 수 있습니다. 포인터는 다음을 수행할 수 없습니다(일부 간접 참조 없이는 제외).

     const int &x = int(12); // legal C++ int *y = &int(12); // illegal to take the address of a temporary.

    const & 를 인수 목록 등에서 사용하는 것을 더 편리하게 만듭니다.


Community Wiki

C++ 참조란 무엇입니까( C 프로그래머용 )

참조 는 자동 간접 참조가 있는 상수 포인터 (상수 값에 대한 포인터와 혼동하지 마십시오!)로 생각할 수 있습니다 * 연산자를 적용합니다.

모든 참조는 null이 아닌 값으로 초기화해야 하며 그렇지 않으면 컴파일이 실패합니다. 참조 주소를 가져오는 것도 불가능합니다. 주소 연산자는 참조된 값의 주소를 대신 반환합니다. 참조에 대한 산술 연산도 불가능합니다.

C 프로그래머는 간접 참조가 발생하거나 인수가 함수 서명을 보지 않고 값이나 포인터로 전달되는 경우 더 이상 명확하지 않기 때문에 C++ 참조를 싫어할 수 있습니다.

C++ 프로그래머는 포인터가 안전하지 않은 것으로 간주되기 때문에 포인터 사용을 싫어할 수 있습니다. 가장 사소한 경우를 제외하고 참조가 상수 포인터보다 실제로 안전하지는 않지만 자동 간접 참조의 편의성이 부족하고 다른 의미를 전달합니다.

C++ FAQ 에서 다음 문장을 고려하십시오.

참조가 기본 어셈블리 언어의 주소를 사용하여 구현되는 경우가 많지만 참조를 개체에 대한 재미있는 포인터로 생각 하지 마십시오. 참조 개체입니다. 객체에 대한 포인터도 아니고 객체의 복사본도 아닙니다. 그것은 개체입니다.

그러나 참조가 실제로 객체라면 어떻게 매달린 참조가 있을 수 있습니까? 관리되지 않는 언어에서는 참조가 포인터보다 '안전'할 수 없습니다. 일반적으로 범위 경계를 넘어 값을 안정적으로 별칭하는 방법이 없습니다!

C++ 참조가 유용하다고 생각하는 이유

는 C의 배경에서 오는, C ++ 참조는 다소 바보 개념으로 보일 수도 있지만, 사람은 여전히 가능 포인터 대신에 그들을 사용한다 : 자동 간접 편리하고 처리 할 때 참조가 특히 유용하게 RAII -하지만 때문에 어떤 인식 안전 이점이 있지만 오히려 관용적 코드 작성을 덜 어색하게 만들기 때문입니다.

RAII는 C++의 핵심 개념 중 하나이지만 복사 의미와 사소하지 않게 상호 작용합니다. 참조로 개체를 전달하면 복사가 포함되지 않으므로 이러한 문제를 피할 수 있습니다. 언어에 참조가 없으면 대신 포인터를 사용해야 하므로 사용이 더 복잡하므로 모범 사례 솔루션이 대안보다 쉬워야 한다는 언어 설계 원칙에 위배됩니다.


Christoph

정말 현학적이고 싶다면 포인터로 할 수 없는 참조로 할 수 있는 한 가지가 있습니다. 바로 임시 객체의 수명을 연장하는 것입니다. C++에서 const 참조를 임시 개체에 바인딩하면 해당 개체의 수명이 참조의 수명이 됩니다.

 std::string s1 = "123"; std::string s2 = "456"; std::string s3_copy = s1 + s2; const std::string& s3_reference = s1 + s2;

이 예에서 s3_copy는 연결의 결과인 임시 객체를 복사합니다. 본질적으로 s3_reference는 임시 객체가 됩니다. 이것은 실제로 참조와 수명이 동일한 임시 개체에 대한 참조입니다.

const 없이 이것을 시도하면 컴파일에 실패해야 합니다. 비 const 참조를 임시 개체에 바인딩할 수 없으며 해당 문제에 대한 주소를 가져올 수도 없습니다.


Matt Price

외에도 문법 설탕에서, 참조는이다 const 포인터 (A로하지 포인터 const ). 참조 변수를 선언할 때 참조하는 항목을 설정해야 하며 나중에 변경할 수 없습니다.

업데이트: 이제 좀 더 생각해보니 중요한 차이점이 있습니다.

const 포인터의 대상은 주소를 가져오고 const 캐스트를 사용하여 바꿀 수 있습니다.

참조의 대상은 UB가 아니면 대체할 수 없습니다.

이렇게 하면 컴파일러가 참조에 대해 더 많은 최적화를 수행할 수 있습니다.


user3458

일반적인 의견과 달리 NULL인 참조를 가질 수 있습니다.

 int * p = NULL; int & r = *p; r = 1; // crash! (if you're lucky)

물론, 참조로 하는 것이 훨씬 더 어렵습니다. 하지만 참조를 관리하면 찾기 위해 머리카락이 찢어질 것입니다. 참조는 C++에서 본질적으로 안전 하지 않습니다!

기술적으로 이것은 null 참조가 아니라 잘못된 참조입니다. C++는 다른 언어에서 찾을 수 있는 개념으로 null 참조를 지원하지 않습니다. 다른 종류의 잘못된 참조도 있습니다. 유효하지 않은 참조는 것 유효하지 않은 포인터를 사용하는 등, 정의되지 않은 행동의 유령을 제기한다.

실제 오류는 참조에 할당하기 전에 NULL 포인터를 역참조하는 데 있습니다. 그러나 해당 조건에서 오류를 생성하는 컴파일러는 알지 못합니다. 오류는 코드의 한 지점으로 전파됩니다. 그것이 이 문제를 그렇게 교활하게 만드는 것입니다. 대부분의 경우 NULL 포인터를 역참조하면 해당 지점에서 충돌이 발생하고 이를 파악하는 데 많은 디버깅이 필요하지 않습니다.

위의 내 예는 짧고 인위적입니다. 다음은 보다 실제적인 예입니다.

 class MyClass { ... virtual void DoSomething(int,int,int,int,int); }; void Foo(const MyClass & bar) { ... bar.DoSomething(i1,i2,i3,i4,i5); // crash occurs here due to memory access violation - obvious why? } MyClass * GetInstance() { if (somecondition) return NULL; ... } MyClass * p = GetInstance(); Foo(*p);

null 참조를 얻을 수 있는 유일한 방법은 잘못된 코드를 사용하는 것이며, 일단 null 참조를 갖게 되면 정의되지 않은 동작이 발생한다는 점을 다시 한 번 강조하고 싶습니다. null 참조를 확인하는 것은 결코 의미가 없습니다. 예를 들어 if(&bar==NULL)... 시도할 수 있지만 컴파일러는 명령문을 존재하지 않게 최적화할 수 있습니다! 유효한 참조는 절대로 NULL이 될 수 없으므로 컴파일러의 관점에서 비교는 항상 거짓이며 if 절을 데드 코드로 제거할 수 있습니다. 이것이 정의되지 않은 동작의 본질입니다.

문제를 피하는 적절한 방법은 참조를 생성하기 위해 NULL 포인터를 역참조하는 것을 피하는 것입니다. 이를 수행하는 자동화된 방법이 있습니다.

 template<typename T> T& deref(T* p) { if (p == NULL) throw std::invalid_argument(std::string("NULL reference")); return *p; } MyClass * p = GetInstance(); Foo(deref(p));

더 나은 작문 기술을 가진 사람이 이 문제에 대해 이전에 살펴본 내용은 Jim Hyslop과 Herb Sutter의 Null References를 참조하십시오.

널 포인터 역참조의 위험에 대한 또 다른 예는 Raymond Chen의 코드를 다른 플랫폼으로 이식하려고 할 때 정의되지 않은 동작 노출을 참조하세요.


Mark Ransom

가장 중요한 부분을 잊어버렸습니다.

포인터를 사용한 멤버 액세스 ->
참조가 있는 구성원 액세스는 를 사용합니다 .

foo.bar 명확히 우수 foo->bar 것과 같은 방식으로 VI가 분명히 우수 이맥스 :-)


Orion Edwards

참조는 포인터와 매우 유사하지만 컴파일러 최적화에 도움이 되도록 특별히 제작되었습니다.

  • 참조는 컴파일러가 어떤 참조 별칭을 어떤 변수로 추적하는 것이 훨씬 더 쉽도록 설계되었습니다. 두 가지 주요 기능이 매우 중요합니다. "참조 산술"이 없고 참조 재할당이 없습니다. 이를 통해 컴파일러는 컴파일 타임에 어떤 변수를 별칭으로 참조하는지 파악할 수 있습니다.
  • 참조는 컴파일러가 레지스터에 넣도록 선택한 것과 같이 메모리 주소가 없는 변수를 참조할 수 있습니다. 지역 변수의 주소를 취하면 컴파일러가 그것을 레지스터에 넣는 것이 매우 어렵습니다.

예로서:

 void maybeModify(int& x); // may modify x in some way void hurtTheCompilersOptimizer(short size, int array[]) { // This function is designed to do something particularly troublesome // for optimizers. It will constantly call maybeModify on array[0] while // adding array[1] to array[2]..array[size-1]. There's no real reason to // do this, other than to demonstrate the power of references. for (int i = 2; i < (int)size; i++) { maybeModify(array[0]); array[i] += array[1]; } }

최적화 컴파일러는 우리가 a[0]과 a[1]에 꽤 많이 접근하고 있다는 것을 깨달을 수 있습니다. 알고리즘을 다음과 같이 최적화하고 싶습니다.

 void hurtTheCompilersOptimizer(short size, int array[]) { // Do the same thing as above, but instead of accessing array[1] // all the time, access it once and store the result in a register, // which is much faster to do arithmetic with. register int a0 = a[0]; register int a1 = a[1]; // access a[1] once for (int i = 2; i < (int)size; i++) { maybeModify(a0); // Give maybeModify a reference to a register array[i] += a1; // Use the saved register value over and over } a[0] = a0; // Store the modified a[0] back into the array }

이러한 최적화를 수행하려면 호출 중에 array[1]을 변경할 수 없음을 증명해야 합니다. 이것은 하기가 오히려 쉽습니다. i는 2보다 작지 않으므로 array[i]는 array[1]을 참조할 수 없습니다. 아마도Modify()는 참조로 0이 제공됩니다(배열[0] 별칭 지정). "참조" 산술이 없기 때문에 컴파일러는 MaybeModify가 x의 주소를 절대 얻지 못한다는 것과 array[1]을 변경하지 않는다는 것을 증명하기만 하면 됩니다.

또한 0에 임시 레지스터 복사본이 있는 동안 향후 호출이 [0]을 읽고 쓸 수 있는 방법이 없음을 증명해야 합니다. 많은 경우 참조가 클래스 인스턴스와 같은 영구적인 구조에 저장되지 않는다는 것이 분명하기 때문에 이것은 증명하기가 쉽지 않습니다.

이제 포인터로 동일한 작업을 수행하십시오.

 void maybeModify(int* x); // May modify x in some way void hurtTheCompilersOptimizer(short size, int array[]) { // Same operation, only now with pointers, making the // optimization trickier. for (int i = 2; i < (int)size; i++) { maybeModify(&(array[0])); array[i] += array[1]; } }

동작은 동일합니다. 우리가 이미 포인터를 제공했기 때문에 이제 mayModify가 array[1]을 수정하지 않는다는 것을 증명하는 것이 훨씬 더 어렵습니다. 고양이가 가방에서 벗어났습니다. 이제 훨씬 더 어려운 증명을 수행해야 합니다. &x + 1에 절대 쓰지 않는다는 것을 증명하기 위해 mayModify의 정적 분석을 수행해야 합니다. 또한 array[0]을 참조할 수 있는 포인터를 저장하지 않는다는 것을 증명해야 합니다. 까다롭게.

최신 컴파일러는 정적 분석에서 점점 더 좋아지고 있지만 항상 컴파일러를 돕고 참조를 사용하는 것이 좋습니다.

물론 이러한 영리한 최적화가 없다면 컴파일러는 필요할 때 참조를 포인터로 바꿀 것입니다.

편집: 이 답변을 게시한 지 5년 후에 참조가 동일한 주소 지정 개념을 보는 다른 방법과 다른 실제 기술적인 차이점을 발견했습니다. 참조는 포인터가 할 수 없는 방식으로 임시 개체의 수명을 수정할 수 있습니다.

 F createF(int argument); void extending() { const F& ref = createF(5); std::cout << ref.getArgument() << std::endl; };

createF(5) 호출로 생성된 것과 같은 임시 객체는 표현식 끝에서 소멸됩니다. 그러나 해당 객체를 참조인 ref ref 가 범위를 벗어날 때까지 해당 임시 객체의 수명을 연장합니다.


Cort Ammon

실제로 참조는 포인터와 다릅니다.

컴파일러는 이름을 메모리 주소와 연결하여 변수에 대한 "참조"를 유지합니다. 컴파일할 때 변수 이름을 메모리 주소로 변환하는 작업입니다.

참조를 생성할 때 포인터 변수에 다른 이름을 할당한다는 사실만 컴파일러에 알립니다. 변수가 될 수도 있고 될 수도 없기 때문에 참조가 "null을 가리킬" 수 없는 이유입니다.

포인터는 변수입니다. 다른 변수의 주소를 포함하거나 null일 수 있습니다. 중요한 것은 포인터에는 값이 있는 반면 참조에는 참조하는 변수만 있다는 것입니다.

이제 실제 코드에 대한 설명:

 int a = 0; int& b = a;

여기서 포인트 것과 다른 변수 생성되지 않습니다 a ; 당신은 단지의 가치 잡고 메모리 내용에 다른 이름을 추가 a . 이 메모리는 이제 두 개의 이름이 와 a b , 그리고 두 이름을 사용하여 해결할 수 있습니다.

 void increment(int& n) { n = n + 1; } int a; increment(a);

함수를 호출할 때 컴파일러는 일반적으로 인수를 복사할 메모리 공간을 생성합니다. 함수 서명은 생성되어야 하는 공간을 정의하고 이러한 공간에 사용해야 하는 이름을 제공합니다. 매개변수를 참조로 선언하면 메서드 호출 중에 새 메모리 공간을 할당하는 대신 입력 변수 메모리 공간을 사용하도록 컴파일러에 지시합니다. 함수가 호출 범위에서 선언된 변수를 직접 조작한다고 말하는 것이 이상하게 보일 수 있지만 컴파일된 코드를 실행할 때 더 이상 범위가 없다는 것을 기억하십시오. 평범한 플랫 메모리만 있고 함수 코드는 모든 변수를 조작할 수 있습니다.

이제 extern 변수를 사용할 때와 같이 컴파일러가 컴파일할 때 참조를 알 수 없는 경우가 있을 수 있습니다. 따라서 참조는 기본 코드에서 포인터로 구현되거나 구현되지 않을 수 있습니다. 그러나 내가 제공한 예에서는 포인터로 구현되지 않을 가능성이 큽니다.


Vincent Robert

NULL 될 수 없습니다.


RichS

참조와 포인터 모두 다른 값에 간접적으로 액세스하는 데 사용되지만 참조와 포인터 사이에는 두 가지 중요한 차이점이 있습니다. 첫 번째는 참조가 항상 개체를 참조한다는 것입니다. 참조를 초기화하지 않고 정의하는 것은 오류입니다. 할당 동작은 두 번째로 중요한 차이점입니다. 참조에 할당하면 참조가 바인딩된 개체가 변경됩니다. 다른 개체에 대한 참조를 다시 바인딩하지 않습니다. 일단 초기화되면 참조는 항상 동일한 기본 개체를 참조합니다.

이 두 프로그램 조각을 고려하십시오. 먼저 포인터 하나를 다른 포인터에 할당합니다.

 int ival = 1024, ival2 = 2048; int *pi = &ival, *pi2 = &ival2; pi = pi2; // pi now points to ival2

ival 할당 후 pi로 주소가 지정된 객체는 변경되지 않은 상태로 유지됩니다. 할당은 파이 값을 변경하여 다른 객체를 가리키도록 합니다. 이제 두 개의 참조를 할당하는 유사한 프로그램을 고려하십시오.

 int &ri = ival, &ri2 = ival2; ri = ri2; // assigns ival2 to ival

이 할당은 참조 자체가 아니라 ri에서 참조하는 값인 ival을 변경합니다. 할당 후에도 두 참조는 여전히 원래 개체를 참조하며 해당 개체의 값도 이제 동일합니다.


Kunal Vyas

추상적이거나 학문적인 방식으로 컴퓨터 언어를 공부하는 데 익숙하지 않은 경우 난해하게 보일 수 있는 의미론적 차이가 있습니다.

최상위 수준에서 참조의 개념은 투명한 "별칭"이라는 것입니다. 컴퓨터는 주소를 사용하여 작동하도록 할 수 있지만 그것에 대해 걱정할 필요는 없습니다. 기존 개체의 "다른 이름"으로 생각해야 하며 구문이 이를 반영합니다. 그것들은 포인터보다 더 엄격하므로 댕글링 포인터를 생성하려고 할 때보다 댕글링 참조를 생성하려고 할 때 컴파일러에서 더 확실하게 경고할 수 있습니다.

그 외에도 포인터와 참조 사이에는 몇 가지 실질적인 차이점이 있습니다. 그것들을 사용하는 구문은 분명히 다르며 참조를 "재장착"하거나, 무에 대한 참조를 가지거나, 참조에 대한 포인터를 가질 수 없습니다.


Lightness Races in Orbit

참조는 다른 변수의 별칭이지만 포인터는 변수의 메모리 주소를 보유합니다. 참조는 일반적으로 전달된 객체가 복사본이 아니라 객체 자체가 되도록 함수 매개변수로 사용됩니다.

 void fun(int &a, int &b); // A common usage of references. int a = 0; int &b = a; // b is an alias for a. Not so common to use.

fatma.ekici

어떤 공간을 차지하든(코드를 실행하지 않고) 실제로 어떤 부작용도 볼 수 없기 때문에 공간을 얼마나 차지하느냐는 중요하지 않습니다.

반면에 참조와 포인터의 한 가지 주요 차이점은 const 참조에 할당된 임시 항목은 const 참조가 범위를 벗어날 때까지 지속된다는 것입니다.

예를 들어:

 class scope_test { public: ~scope_test() { printf("scope_test done!\n"); } }; ... { const scope_test &test= scope_test(); printf("in scope\n"); }

다음을 인쇄합니다:

 in scope scope_test done!

이것은 ScopeGuard가 작동하도록 하는 언어 메커니즘입니다.


MSN

이것은 튜토리얼을 기반으로 합니다. 기록된 내용은 다음과 같이 더 명확합니다.

 >>> The address that locates a variable within memory is what we call a reference to that variable. (5th paragraph at page 63) >>> The variable that stores the reference to another variable is what we call a pointer. (3rd paragraph at page 64)

간단하게 기억해두자면,

 >>> reference stands for memory location >>> pointer is a reference container (Maybe because we will use it for several times, it is better to remember that reference.)

게다가 거의 모든 포인터 튜토리얼을 참조할 수 있듯이 포인터는 포인터를 배열과 유사하게 만드는 포인터 산술에 의해 지원되는 객체입니다.

다음 진술을 보면,

 int Tom(0); int & alias_Tom = Tom;

alias_Tom alias of a variable alias of a type typedef 와 다름) Tom 으로 이해할 수 있습니다. 이러한 문장의 용어를 잊어도 괜찮습니다. Tom


Life

직접 답변

C++에서 참조란 무엇입니까? 개체 유형이 아닌 유형의 일부 특정 인스턴스입니다.

C++에서 포인터란 무엇입니까? 개체 유형 인 유형의 일부 특정 인스턴스입니다.

객체 유형의 ISO C++ 정의에서 :

객체 유형은 함수 유형이 아니고 참조 유형이 아니며 cv void 가 아닌 (아마도 cv -qualified) 유형입니다.

개체 유형이 C++에서 유형 유니버스의 최상위 범주라는 것을 아는 것이 중요할 수 있습니다. 참조는 최상위 범주이기도 합니다. 그러나 포인터는 그렇지 않습니다.

포인터와 참조는 복합 유형 의 맥락에서 함께 언급됩니다. 이것은 기본적으로 참조가 없는 C에서 상속된(및 확장된) 선언자 구문의 특성 때문입니다. (게다가 C++ 11 이후로 참조 선언자가 여러 종류가 있지만 포인터는 여전히 "통합"되어 있습니다. & + && vs. * .) 따라서 이 컨텍스트에서 비슷한 스타일의 C를 사용하여 "확장"으로 특정 언어를 작성합니다. 다소 합리적이다. (나는 여전히 선언자의 구문이 구문적 표현력 을 많이 낭비하고 인간 사용자와 구현 모두를 실망스럽게 만든다고 주장할 것입니다. 따라서 그들 모두는 새로운 언어 디자인에 내장 될 자격이 없습니다. 이것은 완전히 다른 주제입니다. 그러나 PL 디자인에 대해서는.)

그렇지 않으면 포인터가 참조와 함께 특정 유형의 유형으로 규정될 수 있다는 것은 중요하지 않습니다. 그들은 단순히 구문 유사성 외에 너무 적은 공통 속성을 공유하므로 대부분의 경우 함께 사용할 필요가 없습니다.

위의 문장에서는 "포인터"와 "참조"만 유형으로 언급하고 있습니다. 그들의 인스턴스(변수와 같은)에 대한 몇 가지 흥미로운 질문이 있습니다. 또한 너무 많은 오해가 있습니다.

최상위 범주의 차이점은 포인터에 직접 연결되지 않은 많은 구체적인 차이점을 이미 드러낼 수 있습니다.

  • 객체 유형은 최상위 cv 한정자를 가질 수 있습니다. 참조는 할 수 없습니다.
  • 객체 유형의 변수는 추상 기계 의미론에 따라 저장소를 차지합니다. 참조는 저장 공간을 차지할 필요가 없습니다(자세한 내용은 아래의 오해에 대한 섹션 참조).
  • ...

참조에 대한 몇 가지 특별한 규칙:

  • 복합 선언자는 참조에 대해 더 제한적입니다.
  • 참조가 축소 될 수 있습니다.
    • 템플릿 매개변수 추론 중 참조 축소를 기반으로 하는 && 매개변수("전달 참조")에 대한 특수 규칙은 매개변수의 "완벽한 전달" 을 허용합니다.
  • 참조에는 초기화에 특별한 규칙이 있습니다. 참조형으로 선언된 변수의 수명은 확장을 통해 일반 객체와 다를 수 있습니다.
    • std::initializer_list 포함하는 초기화와 같은 몇 가지 다른 컨텍스트는 참조 수명 연장의 몇 가지 유사한 규칙을 따릅니다. 그것은 벌레의 또 다른 캔입니다.
  • ...

오해

구문 설탕

참조가 구문상의 설탕이라는 것을 알고 있으므로 코드를 읽고 쓰기가 더 쉽습니다.

기술적으로 이것은 명백히 잘못된 것입니다. 참조는 의미론적 차이 없이 다른 기능으로 정확히 대체될 수 없기 때문에 C++의 다른 기능의 구문 설탕이 아닙니다.

(마찬가지로, lambda-expression 은 캡처된 변수의 선언 순서 와 같은 "지정되지 않은" 속성으로 정확하게 시뮬레이션할 수 없기 때문에 C++의 다른 기능의 구문 설탕이 아닙니다. 이러한 변수의 초기화 순서가 다음과 같을 수 있기 때문에 중요할 수 있습니다. 중요한.)

C++에는 이 엄격한 의미에서 몇 가지 종류의 구문 설탕만 있습니다. 한 인스턴스는 (C에서 상속됨) 기본 제공(오버로드되지 않은) 연산자 [] . 이는 * 및 이진 + 에 대해 특정 조합 형태의 동일한 의미론적 속성을 정확히 갖도록 정의됩니다 .

저장

따라서 포인터와 참조는 모두 같은 양의 메모리를 사용합니다.

위의 진술은 단순히 잘못된 것입니다. 이러한 오해를 피하려면 대신 ISO C++ 규칙을 살펴보세요.

[intro.object]/1에서 :

... 객체는 생성 기간, 수명 기간 및 파괴 기간 동안 저장 영역을 차지합니다. ...

[dcl.ref]/4에서 :

참조에 저장이 필요한지 여부는 지정되지 않았습니다.

이것들은 의미론적 속성입니다.

화용론

포인터가 언어 디자인의 의미에서 참조와 함께 결합될 만큼 충분히 자격이 없다고 해도, 예를 들어 매개변수 유형을 선택할 때와 같이 다른 컨텍스트에서 포인터 사이에서 선택을 하는 것이 논쟁의 여지가 있는 몇 가지 주장이 여전히 있습니다.

그러나 이것이 전체 이야기는 아닙니다. 포인터와 참조보다 고려해야 할 사항이 더 많습니다.

너무 구체적인 선택을 고수할 필요가 없다면 대부분의 경우 대답은 간단합니다. 포인터를 사용할 필요가 없으므로 . 포인터는 일반적으로 예상하지 못한 너무 많은 것을 암시하고 코드의 유지 관리 가능성과 (심지어) 이식성을 훼손하는 너무 많은 암시적 가정에 의존하기 때문에 충분히 나쁩니다. 불필요하게 포인터에 의존하는 것은 확실히 나쁜 스타일이며 현대 C++의 의미에서 피해야 합니다. 목적을 재고하면 마침내 포인터가 대부분의 경우 마지막 종류의 기능이라는 것을 알게 될 것입니다.

  • 때로는 언어 규칙에 따라 특정 유형을 사용해야 하는 경우가 있습니다. 이러한 기능을 사용하려면 규칙을 준수하십시오.
    • 복사 생성자는 특정 유형의 cv - & 참조 유형을 첫 번째 매개변수 유형으로 요구합니다. (그리고 일반적으로 const 자격이 있어야 합니다.)
    • 이동 생성자는 첫 번째 매개변수 유형으로 특정 유형의 cv - && (그리고 일반적으로 한정자가 없어야 합니다.)
    • 연산자의 특정 오버로드에는 참조 또는 비참조 형식이 필요합니다. 예를 들어:
      • 오버로드된 operator= 특수 멤버 함수로 복사/이동 생성자의 첫 번째 매개변수와 유사한 참조 유형이 필요합니다.
      • 접미사 ++ int 가 필요합니다.
      • ...
  • 값에 의한 전달(즉, 비참조 유형 사용)이 충분하다는 것을 알고 있다면, 특히 C++17 필수 복사 제거를 지원하는 구현을 사용할 때 직접 사용하십시오. ( 경고 : 그러나 그 필요성에 대해 철저하게 추론하는 것은 매우 복잡 할 수 있습니다.)
  • 소유권 일부 핸들을 조작하려는 경우와 같은 스마트 포인터를 사용 unique_ptrshared_ptr (당신이 불투명 될하도록 요구하는 경우 직접 또는과 사제들)을 오히려 원시 포인터보다.
  • 범위에 대해 일부 반복을 수행하는 경우 원시 포인터가 매우 구체적으로 더 나은(예: 헤더 종속성이 적은 경우) 더 나은 결과를 얻을 수 있다고 확신하지 않는 한 원시 포인터 대신 반복자(또는 표준 라이브러리에서 아직 제공하지 않는 일부 범위)를 사용하십시오. 사례.
  • 값에 의한 전달이 충분하고 명시적인 nullable 의미 체계를 원하는 경우 원시 포인터 대신 std::optional
  • 위의 이유로 값에 의한 전달이 이상적이지 않다는 것을 알고 있고 nullable 의미 체계를 원하지 않는 경우 {lvalue, rvalue, forwarding}-참조를 사용하십시오.
  • 전통적인 포인터와 같은 의미 체계를 원하더라도 Library Fundamental TS의 observer_ptr

유일한 예외는 현재 언어로 해결할 수 없습니다.

  • 위의 스마트 포인터를 구현할 때 원시 포인터를 처리해야 할 수도 있습니다.
  • 특정 언어 상호 운용 루틴에는 operator new 와 같은 포인터가 필요합니다. (그러나 cv - void* 는 GNU와 같은 void* 에 대한 비준수 확장에 의존하지 않는 한 예상치 못한 포인터 산술을 배제하기 때문에 일반 객체 포인터와 비교하여 여전히 상당히 다르고 안전합니다.)
  • 함수 포인터는 캡처 없이 람다 식에서 변환할 수 있지만 함수 참조는 변환할 수 없습니다. 의도적으로 nullable 값을 원하지 않더라도 이러한 경우에는 일반 코드가 아닌 코드에서 함수 포인터를 사용해야 합니다.

따라서 실제로 답은 매우 분명합니다. 확실 하지 않은 경우 포인터를 피하십시오 . 더 적절한 것이 없다는 매우 분명한 이유가 있는 경우에만 포인터를 사용해야 합니다. 위에서 언급한 몇 가지 예외적인 경우를 제외하고 이러한 선택은 거의 항상 순수하게 C++에만 국한되지는 않습니다(그러나 언어 구현에 따라 다를 수 있음). 이러한 인스턴스는 다음과 같을 수 있습니다.

  • 구식(C) API를 제공해야 합니다.
  • 특정 C++ 구현의 ABI 요구 사항을 충족해야 합니다.
  • 특정 구현에 대한 가정을 기반으로 런타임 시 다양한 언어 구현(다양한 어셈블리, 언어 런타임 및 일부 고급 클라이언트 언어의 FFI 포함)과 상호 운용해야 합니다.
  • 극단적인 경우 번역(컴파일 및 링크)의 효율성을 높여야 합니다.
  • 일부 극단적인 경우에는 기호 팽창을 피해야 합니다.

언어 중립성 주의 사항

일부 Google 검색 결과(C++에만 해당되지 않음) 를 통해 질문을 보게 된다면 이것은 잘못된 위치일 가능성이 매우 높습니다.

C++의 참조는 본질적으로 일급이 아니기 때문에 상당히 "이상"합니다. 참조되는 객체 또는 함수로 취급 되므로 왼쪽 피연산자가 되는 것과 같은 일부 일급 연산을 지원할 기회가 없습니다 . 참조된 개체의 형식과 독립적으로 멤버 액세스 연산자입니다. 다른 언어는 참조에 대해 유사한 제한이 있을 수도 있고 없을 수도 있습니다.

C++의 참조는 다른 언어에서 의미를 유지하지 못할 수 있습니다. 예를 들어, 일반적으로 참조는 C++에서와 같이 값에 대해 null이 아닌 속성을 암시하지 않으므로 이러한 가정은 일부 다른 언어에서는 작동하지 않을 수 있습니다(예: Java, C#, ...).

일반적으로 다른 프로그래밍 언어의 참조 사이에는 여전히 몇 가지 공통 속성이 있을 수 있지만 SO의 다른 질문에 대해서는 그대로 두겠습니다.

(참고: 질문은 ALGOL 68 대 PL/I 와 같은 "C 유사" 언어가 관련된 것보다 더 일찍 중요할 수 있습니다.)


FrankHB

참조는 일부 메모리에 부여된 다른 이름이 아닙니다. 사용 시 자동으로 역참조되는 불변 포인터입니다. 기본적으로 다음과 같이 요약됩니다.

 int& j = i;

내부적으로 된다

 int* const j = &i;

tanweer alam

포인터에 대한 참조는 C++에서 가능하지만 반대가 불가능하다는 것은 참조에 대한 포인터가 불가능하다는 것을 의미합니다. 포인터에 대한 참조는 포인터를 수정하는 더 깔끔한 구문을 제공합니다. 이 예를 보십시오.

 #include<iostream> using namespace std; void swap(char * &str1, char * &str2) { char *temp = str1; str1 = str2; str2 = temp; } int main() { char *str1 = "Hi"; char *str2 = "Hello"; swap(str1, str2); cout<<"str1 is "<<str1<<endl; cout<<"str2 is "<<str2<<endl; return 0; }

그리고 위 프로그램의 C 버전을 고려하십시오. C에서는 포인터에 대한 포인터(다중 간접 참조)를 사용해야 하며, 이는 혼란을 야기하고 프로그램이 복잡해 보일 수 있습니다.

 #include<stdio.h> /* Swaps strings by swapping pointers */ void swap1(char **str1_ptr, char **str2_ptr) { char *temp = *str1_ptr; *str1_ptr = *str2_ptr; *str2_ptr = temp; } int main() { char *str1 = "Hi"; char *str2 = "Hello"; swap1(&str1, &str2); printf("str1 is %s, str2 is %s", str1, str2); return 0; }

포인터 참조에 대한 자세한 내용은 다음을 참조하십시오.

내가 말했듯이 참조에 대한 포인터는 불가능합니다. 다음 프로그램을 시도하십시오.

 #include <iostream> using namespace std; int main() { int x = 10; int *ptr = &x; int &*ptr1 = ptr; }

Destructor

포인터와 참조 사이에는 아무도 언급하지 않은 한 가지 근본적인 차이점이 있습니다. 참조는 함수 인수에서 참조에 의한 전달 의미 체계를 가능하게 합니다. 포인터는 처음에는 보이지 않지만 값에 의한 의미 체계만 제공합니다. 이것 은 이 기사 에 아주 잘 설명되어 있습니다 .

안부, &rzej


Andrzej

다음 중 하나가 필요하지 않으면 참조를 사용합니다.

  • 널 포인터는 센티넬 값으로 사용될 수 있으며, 종종 함수 오버로딩이나 bool 사용을 피하기 위한 저렴한 방법입니다.

  • 포인터에 대해 산술을 수행할 수 있습니다. 예를 들어, p += offset;


Aardvark

혼란을 더할 위험이 있으므로 일부 입력을 던지고 싶습니다. 대부분 컴파일러가 참조를 구현하는 방법에 따라 다르지만 gcc의 경우 참조는 스택의 변수만 가리킬 수 있다는 생각입니다. 실제로 올바르지 않습니다. 예를 들면 다음과 같습니다.

 #include <iostream> int main(int argc, char** argv) { // Create a string on the heap std::string *str_ptr = new std::string("THIS IS A STRING"); // Dereference the string on the heap, and assign it to the reference std::string &str_ref = *str_ptr; // Not even a compiler warning! At least with gcc // Now lets try to print it's value! std::cout << str_ref << std::endl; // It works! Now lets print and compare actual memory addresses std::cout << str_ptr << " : " << &str_ref << std::endl; // Exactly the same, now remember to free the memory on the heap delete str_ptr; }

다음을 출력합니다.

 THIS IS A STRING 0xbb2070 : 0xbb2070

메모리 주소도 정확히 동일하다는 것을 알게 되면 참조가 힙의 변수를 성공적으로 가리키고 있음을 의미합니다! 이제 정말로 괴상해지기를 원한다면 다음도 작동합니다.

 int main(int argc, char** argv) { // In the actual new declaration let immediately de-reference and assign it to the reference std::string &str_ref = *(new std::string("THIS IS A STRING")); // Once again, it works! (at least in gcc) std::cout << str_ref; // Once again it prints fine, however we have no pointer to the heap allocation, right? So how do we free the space we just ignorantly created? delete &str_ref; /*And, it works, because we are taking the memory address that the reference is storing, and deleting it, which is all a pointer is doing, just we have to specify the address with '&' whereas a pointer does that implicitly, this is sort of like calling delete &(*str_ptr); (which also compiles and runs fine).*/ }

다음을 출력합니다.

 THIS IS A STRING

따라서 참조는 후드 아래의 포인터이며 둘 다 메모리 주소를 저장하고 있습니다. 주소가 가리키는 곳은 관련이 없습니다. std::cout << str_ref; delete &str_ref를 호출한 후? 글쎄, 분명히 잘 컴파일되지만 더 이상 유효한 변수를 가리키지 않기 때문에 런타임에 분할 오류가 발생합니다. 본질적으로 여전히 존재하지만 (범위를 벗어날 때까지) 쓸모없는 깨진 참조가 있습니다.

다시 말해, 참조는 포인터 역학을 추상화한 포인터에 불과하여 더 안전하고 사용하기 쉽게 만듭니다(우발적인 포인터 수학, '.' 및 '->' 혼합 없음 등). 위의 예와 같이 말도 안되는 소리를 시도하지 마십시오. ;)

이제 컴파일러가 참조를 처리하는 방법에 관계없이 항상 내부에 일종의 포인터가 있습니다. 참조 예상대로 작동하려면 특정 메모리 주소의 특정 변수를 참조해야 하기 때문에 이 문제를 해결할 방법이 없습니다(따라서 '참조'라는 용어).

참조와 함께 기억해야 할 중요한 유일한 주요 규칙은 선언 시 정의되어야 한다는 것입니다(헤더의 참조를 제외하고, 이 경우 참조가 포함된 객체 이후에 생성자에서 정의해야 합니다. 정의하기에는 너무 늦었습니다).

위의 예는 참조가 무엇인지 보여주는 예일 뿐이므로 참조를 그런 식으로 사용하고 싶지는 않을 것입니다! 참조의 적절한 사용을 위해 여기에 이미 머리에 못을 박은 답변이 많이 있습니다.


Tory

또 다른 차이점은 void 유형에 대한 포인터를 가질 수 있지만(무엇이든에 대한 포인터를 의미함) void에 대한 참조는 금지된다는 것입니다.

 int a; void * p = &a; // ok void & p = a; // forbidden

이 특별한 차이에 정말 만족한다고 말할 수는 없습니다. 주소가 있는 모든 항목에 대한 의미 참조와 참조에 대한 동일한 동작이 허용되는 것을 훨씬 선호합니다. 참조를 사용하여 memcpy와 같은 C 라이브러리 함수의 일부를 정의할 수 있습니다.


kriss

또한 인라인된 함수에 대한 매개변수인 참조는 포인터와 다르게 처리될 수 있습니다.

 void increment(int *ptrint) { (*ptrint)++; } void increment(int &refint) { refint++; } void incptrtest() { int testptr=0; increment(&testptr); } void increftest() { int testref=0; increment(testref); }

포인터 버전 1을 인라인할 때 많은 컴파일러는 실제로 메모리에 쓰기를 강제합니다(주소를 명시적으로 가져옴). 그러나 그들은 더 최적의 레지스터에 참조를 남길 것입니다.

물론 인라인되지 않은 함수의 경우 포인터와 참조는 동일한 코드를 생성하며 함수에서 수정 및 반환하지 않는 경우 내장을 참조보다 값으로 전달하는 것이 항상 더 좋습니다.


Adisak

참조의 또 다른 흥미로운 용도는 사용자 정의 유형의 기본 인수를 제공하는 것입니다.

 class UDT { public: UDT() : val_d(33) {}; UDT(int val) : val_d(val) {}; virtual ~UDT() {}; private: int val_d; }; class UDT_Derived : public UDT { public: UDT_Derived() : UDT() {}; virtual ~UDT_Derived() {}; }; class Behavior { public: Behavior( const UDT &udt = UDT() ) {}; }; int main() { Behavior b; // take default UDT u(88); Behavior c(u); UDT_Derived ud; Behavior d(ud); return 1; }

기본 플레이버는 참조의 '임시로 const 참조 바인딩' 측면을 사용합니다.


Don Wakefield

이 프로그램은 질문에 대한 답을 이해하는 데 도움이 될 수 있습니다. 이것은 참조 "j"와 변수 "x"를 가리키는 포인터 "ptr"의 간단한 프로그램입니다.

 #include<iostream> using namespace std; int main() { int *ptr=0, x=9; // pointer and variable declaration ptr=&x; // pointer to variable "x" int & j=x; // reference declaration; reference to variable "x" cout << "x=" << x << endl; cout << "&x=" << &x << endl; cout << "j=" << j << endl; cout << "&j=" << &j << endl; cout << "*ptr=" << *ptr << endl; cout << "ptr=" << ptr << endl; cout << "&ptr=" << &ptr << endl; getch(); }

프로그램을 실행하고 출력을 보면 이해할 수 있습니다.

또한 10분 정도 시간을 내어 이 동영상을 시청 하세요. https://www.youtube.com/watch?v=rlJrrGV0iOg


Arlene Batada

여기에서 다루지 않은 또 다른 점이 있는 것 같습니다.

포인터와 달리 참조는 참조 하는 개체와 구문적으로 동일 합니다. 즉 개체에 적용할 수 있는 모든 작업은 참조에 대해 작동하며 정확히 동일한 구문을 사용합니다(물론 초기화는 예외입니다).

이것은 피상적으로 보일 수 있지만 이 속성은 다음과 같은 여러 C++ 기능에 중요하다고 생각합니다.

  • 템플릿 . 템플릿 매개변수는 오리 형식이므로 형식의 구문 속성이 모두 중요하므로 종종 동일한 템플릿을 TT& 둘 다에 사용할 수 있습니다.
    (또는 T& 에 대한 암시적 캐스트에 여전히 의존하는 std::reference_wrapper<T> )
    모두 다루 템플릿 T&T&& 더 일반적이다.

  • L값 . str[0] = 'X'; 참조가 없으면 c-문자열( char* str )에서만 작동합니다. 참조로 문자를 반환하면 사용자 정의 클래스가 동일한 표기법을 가질 수 있습니다.

  • 복사 생성자 . 구문상 개체에 대한 포인터가 아니라 복사 생성자에 개체를 전달하는 것이 합리적입니다. 그러나 복사 생성자가 값으로 개체를 가져오는 방법은 없습니다. 동일한 복사 생성자에 대한 재귀 호출이 발생합니다. 이것은 여기에서 유일한 옵션으로 참조를 남깁니다.

  • 연산자 과부하 . 참조를 사용하면 동일한 중위 표기법을 유지하면서 operator+(const T& a, const T& b) 같이 연산자 호출에 간접 참조를 도입할 수 있습니다. 이것은 일반 오버로드된 함수에서도 작동합니다.

이러한 점은 C++ 및 표준 라이브러리의 상당 부분을 강화하므로 이것이 참조의 주요 속성입니다.


Ap31

포인터와 참조 사이에는 매우 중요한 비기술적 차이가 있습니다. 포인터에 의해 함수에 전달된 인수는 비 const 참조에 의해 함수에 전달된 인수보다 훨씬 더 잘 보입니다. 예를 들어:

 void fn1(std::string s); void fn2(const std::string& s); void fn3(std::string& s); void fn4(std::string* s); void bar() { std::string x; fn1(x); // Cannot modify x fn2(x); // Cannot modify x (without const_cast) fn3(x); // CAN modify x! fn4(&x); // Can modify x (but is obvious about it) }

fn(x) 처럼 보이는 호출은 값으로만 전달할 수 있으므로 x 수정할 수 없습니다. fn(&x) 포인터를 전달해야 합니다. 따라서 인수 앞에 & 가 없으면 수정되지 않을 것임을 알았습니다. (반대, & const 포인터로 큰 읽기 전용 구조를 전달해야 하기 때문에 사실이 아닙니다.)

어떤 사람들은 이것이 코드를 읽을 때 매우 유용한 기능이며, 함수가 nullptr const 참조보다는 수정 가능한 매개변수에 사용해야 한다고 주장합니다. 즉, 그 사람들은 fn3() 과 같은 함수 서명을 허용해서는 안 된다고 주장합니다. Google의 C++ 스타일 지침 이 그 예입니다.


Arthur Tacca

아마도 몇 가지 비유가 도움이 될 것입니다. 데스크탑 화면 공간의 맥락에서 -

  • 참조를 사용하려면 실제 창을 지정해야 합니다.
  • 포인터를 사용하려면 해당 창 유형의 인스턴스가 0개 이상 포함될 것임을 보증하는 화면상의 공간 위치가 필요합니다.

George R

포인터와 참조의 차이점

포인터는 0으로 초기화할 수 있고 참조는 초기화할 수 없습니다. 사실 참조는 객체도 참조해야 하지만 포인터는 널 포인터가 될 수 있습니다.

 int* p = 0;

그러나 우리는 int& p = 0; 또한 int& p=5 ; .

사실 제대로 하려면 먼저 객체를 선언하고 정의해야 하고 그 객체에 대한 참조를 만들 수 있으므로 이전 코드의 올바른 구현은 다음과 같습니다.

 Int x = 0; Int y = 5; Int& p = x; Int& p1 = y;

또 다른 중요한 점은 초기화 없이 포인터를 선언할 수 있지만 항상 변수나 객체를 참조해야 하는 참조의 경우에는 그렇게 할 수 없다는 것입니다. 그러나 이러한 포인터 사용은 위험하므로 일반적으로 포인터가 실제로 무엇을 가리키는지 여부를 확인합니다. 참조의 경우 이러한 확인이 필요하지 않습니다. 선언하는 동안 객체를 참조하는 것이 필수라는 것을 이미 알고 있기 때문입니다.

또 다른 차이점은 포인터가 다른 객체를 가리킬 수 있지만 참조는 항상 동일한 객체를 참조한다는 점입니다. 이 예를 들어보겠습니다.

 Int a = 6, b = 5; Int& rf = a; Cout << rf << endl; // The result we will get is 6, because rf is referencing to the value of a. rf = b; cout << a << endl; // The result will be 5 because the value of b now will be stored into the address of a so the former value of a will be erased

또 다른 요점: STL 템플릿과 같은 템플릿이 있는 경우 이러한 종류의 클래스 템플릿은 항상 포인터가 아닌 참조를 반환하여 연산자 []를 사용하여 새 값을 쉽게 읽거나 할당할 수 있도록 합니다.

 Std ::vector<int>v(10); // Initialize a vector with 10 elements V[5] = 5; // Writing the value 5 into the 6 element of our vector, so if the returned type of operator [] was a pointer and not a reference we should write this *v[5]=5, by making a reference we overwrite the element by using the assignment "="

dhokar.w

참조 및 포인터에 대한 몇 가지 주요 관련 세부정보

포인터

  • 포인터 변수는 단항 접미사 선언자 연산자 *를 사용하여 선언됩니다.
  • 포인터 개체에는 주소 값이 할당됩니다(예: 배열 개체에 할당, & 단항 접두사 연산자를 사용하는 개체 주소 또는 다른 포인터 개체 값에 할당).
  • 포인터는 다른 개체를 가리키는 여러 번 재할당될 수 있습니다.
  • 포인터는 할당된 주소를 보유하는 변수입니다. 대상 시스템 아키텍처의 주소 크기와 동일한 메모리 저장 공간을 차지합니다.
  • 포인터는 예를 들어 증가 또는 더하기 연산자를 사용하여 수학적으로 조작할 수 있습니다. 따라서 포인터 등으로 반복할 수 있습니다.
  • 포인터가 참조하는 객체의 내용을 가져오거나 설정하려면 단항 접두사 연산자 *를 사용하여 역참조 해야 합니다.

참고문헌

  • 참조는 선언될 때 초기화되어야 합니다.
  • 참조는 단항 접미사 선언자 연산자 &를 사용하여 선언됩니다.
  • 참조를 초기화할 때 단항 접두사 연산자 & 없이 직접 참조할 개체의 이름을 사용합니다.
  • 일단 초기화되면 참조는 할당이나 산술 조작으로 다른 것을 가리킬 수 없습니다.
  • 참조하는 개체의 내용을 가져오거나 설정하기 위해 참조를 역참조할 필요가 없습니다.
  • 참조에 대한 할당 작업은 참조 자체가 아니라 참조가 가리키는 개체의 내용(초기화 후)을 조작합니다(참조 위치는 변경되지 않음).
  • 참조에 대한 산술 연산은 참조 자체가 아니라 참조가 가리키는 객체의 내용을 조작합니다(참조 위치는 변경되지 않음).
  • 거의 모든 구현에서 참조는 실제로 참조된 개체의 메모리에 주소로 저장됩니다. 따라서 포인터 객체와 마찬가지로 대상 시스템 아키텍처의 주소 크기와 동일한 메모리의 저장소를 차지합니다.

포인터와 참조가 "under-the-hood"와 거의 동일한 방식으로 구현되더라도 컴파일러는 이들을 다르게 취급하므로 위에서 설명한 모든 차이점이 발생합니다.

기사

내가 쓴 최근 기사는 여기에서 보여드릴 수 있는 것보다 훨씬 더 자세하게 설명되어 있으며 특히 메모리에서 일이 어떻게 일어나는지에 대한 질문에 매우 도움이 될 것입니다.

내부의 어레이, 포인터 및 참조 심층 기사


Xitalogy

참조는 const 포인터입니다. int * const a = &bint& a = b . 이것이 const 참조와 같은 것이 없는 이유입니다. 이미 const이기 때문입니다. 반면 const에 대한 참조는 const int * const a 입니다. -O0을 사용하여 컴파일하면 컴파일러는 두 상황 모두에서 b의 주소를 스택에 배치하고 클래스의 멤버로서 a를 선언한 경우와 동일하게 스택/힙의 개체에도 표시됩니다. 상수 포인터. -Ofast를 사용하면 이를 최적화할 수 있습니다. const 포인터와 참조는 모두 최적화되어 있습니다.

const 포인터와 달리 참조 주소는 참조하는 변수의 주소로 해석되므로 참조 자체의 주소를 가져올 수 없습니다. 이 때문에 -Ofast에서 참조를 나타내는 const 포인터(참조되는 변수의 주소)는 항상 스택에서 최적화되지만 프로그램이 실제 const 포인터의 주소(포인터의 주소 즉, const 포인터의 주소를 인쇄하면 const 포인터가 주소를 갖도록 스택에 배치됩니다.

그렇지 않으면 동일합니다. 즉, 해당 주소를 인쇄할 때 가리키는 주소는 다음과 같습니다.

 #include <iostream> int main() { int a =1; int* b = &a; std::cout << b ; } int main() { int a =1; int& b = a; std::cout << &b ; }
 they both have the same assembly output -Ofast: main: sub rsp, 24 mov edi, OFFSET FLAT:_ZSt4cout lea rsi, [rsp+12] mov DWORD PTR [rsp+12], 1 call std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<void const*>(void const*) xor eax, eax add rsp, 24 ret -------------------------------------------------------------------- -O0: main: push rbp mov rbp, rsp sub rsp, 16 mov DWORD PTR [rbp-12], 1 lea rax, [rbp-12] mov QWORD PTR [rbp-8], rax mov rax, QWORD PTR [rbp-8] mov rsi, rax mov edi, OFFSET FLAT:_ZSt4cout call std::basic_ostream<char, std::char_traits<char> >::operator<<(void const*) mov eax, 0 leave ret

포인터는 스택에서 최적화되었으며 포인터는 두 경우 모두 -Ofast에서 역참조되지 않고 대신 컴파일 시간 값을 사용합니다.

개체의 구성원으로서 -O0에서 -Ofast까지 동일합니다.

 #include <iostream> int b=1; struct A {int* i=&b; int& j=b;}; A a; int main() { std::cout << &a.j << &a.i; } The address of b is stored twice in the object. a: .quad b .quad b
 mov rax, QWORD PTR a[rip+8] //&a.j mov esi, OFFSET FLAT:a //&a.i

-O0에서 참조로 전달할 때 참조된 변수의 주소를 전달하므로 포인터로 전달하는 것과 동일합니다. 즉, const 포인터가 포함하는 주소입니다. On -Ofast 동적 범위가 알려져 있으므로 함수를 인라인할 수 있는 경우 인라인 호출에서 컴파일러에 의해 최적화되지만 함수 정의에서 매개변수는 항상 포인터로 역참조됩니다(변수의 주소가 참조에 의해 참조됨) 다른 번역 단위에서 사용할 수 있고 동적 범위가 컴파일러에 알려지지 않은 경우 물론 함수가 정적 함수로 선언되지 않는 한 번역 단위 외부에서 사용할 수 없으며 그런 다음 참조에 의해 함수에서 수정되지 않는 한 값으로 전달됩니다. 그러면 전달하는 참조에서 참조하는 변수의 주소를 전달하고 on -Ofast에서 이것은 레지스터에 전달되고 호출 규칙에 휘발성 레지스터가 충분하면 스택에서 제외됩니다.


Lewis Kelsey

출처 : http:www.stackoverflow.com/questions/57483/what-are-the-differences-between-a-pointer-variable-and-a-reference-variable-in

반응형