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



Answers

C ++ 참고 자료 ( C 프로그래머 용 )

참조상수 포인터 (상수 값에 대한 포인터와 혼동해서는 안됩니다!)와 자동 간접 지정으로 생각할 수 있습니다. 즉, 컴파일러는 * 연산자를 적용합니다.

모든 참조는 널이 아닌 값으로 초기화되어야하며 그렇지 않으면 컴파일이 실패합니다. 참조 주소를 얻는 것도 불가능합니다. 대신 주소 연산자가 참조 된 값의 주소를 반환합니다. 참조에 대해 연산을 수행 할 수도 없습니다.

C 프로그래머는 간접적 인 일이 발생하거나 인수가 함수 시그니처를 보지 않고 값이나 포인터로 전달되는 경우 더 이상 명확하지 않으므로 C ++ 참조를 싫어할 수 있습니다.

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

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

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

그러나 참조가 실제로 대상 일 경우 참조가 매달릴 수 있습니까? 관리되지 않는 언어에서는 참조가 포인터보다 '더 안전'하지 못합니다. 일반적으로 범위 경계에서 값을 신뢰할 수있게 별칭을 지정하는 방법이 아닙니다.

왜 내가 C ++ 참조가 유용하다고 생각 하는가?

C 배경에서 나온 C ++ 참조는 다소 어리석은 개념처럼 보이지만 가능하면 포인터 대신 자동 참조가 사용되어야합니다. 자동 간접 참조가 편리하며 참조가 RAII 처리 할 때 특히 유용하지만 인식 된 안전성 때문에 그렇지 않습니다 장점 이라기보다는 관용구 코드를 덜 어색하게 만든다.

RAII는 C ++의 핵심 개념 중 하나이지만, 의미를 복사하는 것과는 거의 상호 작용하지 않습니다. 참조로 개체를 전달하면 복사가 필요 없으므로 이러한 문제를 피할 수 있습니다. 언어에서 참조가 없으면 대신 포인터를 사용해야하므로 사용하기가 번거롭므로 대안보다 모범 사례 솔루션이 쉬워야한다는 언어 디자인 원칙을 위반합니다.

Question

나는 참조가 문법적인 설탕이라는 것을 안다. 그래서 코드는 읽고 쓰는 것이 더 쉽다.

그러나 차이점은 무엇입니까?

아래 답변 및 링크 요약 :

  1. 참조 후 바인딩을 다시 할 수없는 동안 포인터는 여러 번 다시 할당 할 수 있습니다.
  2. 포인터는 아무데도 가리킬 수 없으며 ( NULL ), 참조는 항상 객체를 참조합니다.
  3. 포인터로 할 수있는 것처럼 참조 주소를 가져올 수 없습니다.
  4. "참조 연산"은 없습니다 (그러나 참조로 가리키는 오브젝트의 주소를 가져 와서 &obj + 5 와 같이 포인터 연산을 수행 할 수 있습니다).

오해를 명확히하기 위해 :

C ++ 표준은 컴파일러가 참조를 구현해야하는 방법을 피하기 위해 매우 신중하지만 모든 C ++ 컴파일러는 참조를 포인터로 구현합니다. 즉, 다음과 같은 선언입니다.

int &ri = i;

완전히 최적화되지 않은 경우 포인터와 동일한 양의 저장소를 할당하고 해당 주소를 해당 저장소에 저장합니다.

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

일반적으로,

  • 유용하고 자기 - 문서화 된 인터페이스를 정의하기 위해 함수 매개 변수와 리턴 타입에서 참조를 사용하십시오.
  • 포인터를 사용하여 알고리즘과 데이터 구조를 구현하십시오.

재미있는 읽기 :




대중적인 의견과는 달리, 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 참조를 확인하는 것은 결코 의미가 없습니다 . 예를 들어 if(&bar==NULL)... 시도해 볼 수는 있지만 if(&bar==NULL)... 컴파일러가 문을 최적화하지 못할 수도 있습니다! 유효한 참조는 NULL이 될 수 없으므로 컴파일러의 뷰에서 비교가 항상 거짓이며 if 절을 불필요한 코드로 제거 할 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 참조참조 하십시오.

NULL 포인터 역 참조의 위험에 대한 또 다른 예제는 레이몬드 첸 (Raymond Chen)이 다른 플랫폼코드를 포팅하려고 할 때 정의되지 않은 동작 노출 입니다.




It doesn't matter how much space it takes up since you can't actually see any side effect (without executing code) of whatever space it would take up.

On the other hand, one major difference between references and pointers is that temporaries assigned to const references live until the const reference goes out of scope.

예 :

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!

This is the language mechanism that allows ScopeGuard to work.




The difference is that non-constant pointer variable(not to be confused with a pointer to constant) may be changed at some time during program execution, requires pointer semantics to be used(&,*) operators, while references can be set upon initialization only(that's why you can set them in constructor initializer list only, but not somehow else) and use ordinary value accessing semantics. Basically references were introduced to allow support for operators overloading as I had read in some very old book. As somebody stated in this thread - pointer can be set to 0 or whatever value you want. 0(NULL, nullptr) means that the pointer is initialized with nothing. It is an error to dereference null pointer. But actually the pointer may contain a value that doesn't point to some correct memory location. References in their turn try not to allow a user to initialize a reference to something that cannot be referenced due to the fact that you always provide rvalue of correct type to it. Although there are a lot of ways to make reference variable be initialized to a wrong memory location - it is better for you not to dig this deep into details. On machine level both pointer and reference work uniformly - via pointers. Let's say in essential references are syntactic sugar. rvalue references are different to this - they are naturally stack/heap objects.




At the risk of adding to confusion, I want to throw in some input, I'm sure it mostly depends on how the compiler implements references, but in the case of gcc the idea that a reference can only point to a variable on the stack is not actually correct, take this for example:

#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;
}

Which outputs this:

THIS IS A STRING
0xbb2070 : 0xbb2070

If you notice even the memory addresses are exactly the same, meaning the reference is successfully pointing to a variable on the heap! Now if you really want to get freaky, this also works:

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).*/
}

Which outputs this:

THIS IS A STRING

Therefore a reference IS a pointer under the hood, they both are just storing a memory address, where the address is pointing to is irrelevant, what do you think would happen if I called std::cout << str_ref; AFTER calling delete &str_ref? Well, obviously it compiles fine, but causes a segmentation fault at runtime because it's no longer pointing at a valid variable, we essentially have a broken reference that still exists (until it falls out of scope), but is useless.

In other words, a reference is nothing but a pointer that has the pointer mechanics abstracted away, making it safer and easier to use (no accidental pointer math, no mixing up '.' and '->', etc.), assuming you don't try any nonsense like my examples above ;)

Now regardless of how a compiler handles references, it will always have some kind of pointer under the hood, because a reference must refer to a specific variable at a specific memory address for it to work as expected, there is no getting around this (hence the term 'reference').

The only major rule that's important to remember with references is that they must be defined at the time of declaration (with the exception of a reference in a header, in that case it must be defined in the constructor, after the object it's contained in is constructed it's too late to define it).

Remember, my examples above are just that, examples demonstrating what a reference is, you would never want to use a reference in those ways! For proper usage of a reference there are plenty of answers on here already that hit the nail on the head




A reference to a pointer is possible in C++, but the reverse is not possible means a pointer to a reference isn't possible. A reference to a pointer provides a cleaner syntax to modify the pointer. Look at this example:

#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;
}

And consider the C version of the above program. In C you have to use pointer to pointer (multiple indirection), and it leads to confusion and the program may look complicated.

#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;
}

Visit the following for more information about reference to pointer:

As I said, a pointer to a reference isn't possible. Try the following program:

#include <iostream>
using namespace std;

int main()
{
   int x = 10;
   int *ptr = &x;
   int &*ptr1 = ptr;
}



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

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

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

할당이 끝나면 pi에 의해 처리 된 객체는 변경되지 않습니다. 할당은 pi 값을 변경하여 다른 객체를 가리 킵니다. 이제 두 개의 참조를 지정하는 유사한 프로그램을 고려하십시오.

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

This assignment changes ival, the value referenced by ri, and not the reference itself. After the assignment, the two references still refer to their original objects, and the value of those objects is now the same as well.




구문 당과는 별도로, 참조는 const 포인터입니다 ( const 포인터가 아닙니다 ). 참조 변수를 선언 할 때 참조하는 내용을 설정해야하며 나중에 변경할 수 없습니다.

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

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

참조의 대상은 UB가 부족한 어떤 방법 으로든 대체 할 수 없습니다.

이렇게하면 컴파일러가 참조에서 더 많은 최적화 작업을 수행 할 수 있습니다.




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

  • 참조는 컴파일러가 어떤 참조 별칭을 추적하는지 쉽게 추적 할 수 있도록 설계되었습니다. "참조 연산"이없고 참조를 재 할당하지 않는 두 가지 주요 기능이 매우 중요합니다. 이것들은 컴파일러가 어떤 참조를 컴파일 할 때 어떤 변수에 알릴지를 알 수있게합니다.
  • 참조는 컴파일러가 레지스터에 넣는 것과 같이 메모리 주소가없는 변수를 참조 할 수 있습니다. 지역 변수의 주소를 취하면 컴파일러가 레지스터에 넣기가 매우 어렵습니다.

예로서:

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];
    }
}

최적화 컴파일러는 우리가 [0]과 [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
}

이러한 최적화를 수행하려면 호출 중에 배열 [1]을 변경할 수있는 것이 없음을 증명해야합니다. 이것은하기 쉽습니다. 나는 결코 2보다 작지 않으므로 array [i]는 배열 [1]을 결코 참조 할 수 없다. maybeModify ()는 a0을 참조로 제공합니다 (별칭 배열 [0]). "참조"연산이 없기 때문에 컴파일러는 maybeModify가 x의 주소를 얻지 못한다는 것을 증명해야하며 배열 [1]을 변경하지 않는다는 것이 증명되었습니다.

또한 a0에 임시 레지스터 복사본이있는 동안 향후 호출이 [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];
    }
}

동작은 동일합니다. maybeModify가 배열 [1]을 수정하지 않는다는 것을 증명하는 것이 훨씬 어렵습니다. 왜냐하면 우리가 이미 포인터를 주었기 때문입니다. 고양이는 가방에서 나와요. 이제는 훨씬 더 어려운 증명을해야합니다 : xM + 1에 쓰지 않는 것을 증명하는 maybeModify의 정적 분석. 배열 [0]을 참조 할 수있는 포인터를 절대로 저장하지 않아야한다는 것을 증명해야합니다. 까다로워.

현대 컴파일러는 정적 분석에서 더 좋아지고 있습니다.하지만 참조를 사용하고 도움을주는 것이 좋습니다.

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




참조와 포인터에 대한 비유가 있는데, 참조는 객체의 다른 이름으로, 포인터는 객체의 주소로 생각합니다.

// receives an alias of an int, an address of an int and an int value
public void my_function(int& a,int* b,int c){
    int d = 1; // declares an integer named d
    int &e = d; // declares that e is an alias of d
    // using either d or e will yield the same result as d and e name the same object
    int *f = e; // invalid, you are trying to place an object in an address
    // imagine writting your name in an address field 
    int *g = f; // writes an address to an address
    g = &d; // &d means get me the address of the object named d you could also
    // use &e as it is an alias of d and write it on g, which is an address so it's ok
}



I feel like there is yet another point that hasn't been covered here.

Unlike the pointers, references are syntactically equivalent to the object they refer to, ie any operation that can be applied to an object works for a reference, and with the exact same syntax (the exception is of course the initialization).

While this may appear superficial, I believe this property is crucial for a number of C++ features, for example:

  • Templates . Since template parameters are duck-typed, syntactic properties of a type is all that matters, so often the same template can be used with both T and T& .
    (or std::reference_wrapper<T> which still relies on an implicit cast to T& )
    Templates that cover both T& and T&& are even more common.

  • Lvalues . Consider the statement str[0] = 'X'; Without references it would only work for c-strings ( char* str ). Returning the character by reference allows user-defined classes to have the same notation.

  • Copy constructors . Syntactically it makes sense to pass objects to copy constructors, and not pointers to objects. But there is just no way for a copy constructor to take an object by value - it would result in a recursive call to the same copy constructor. This leaves references as the only option here.

  • Operator overloads . With references it is possible to introduce indirection to an operator call - say, operator+(const T& a, const T& b) while retaining the same infix notation. This also works for regular overloaded functions.

These points empower a considerable part of C++ and the standard library so this is quite a major property of references.




Both references and pointers can be used to change local variables of one function inside another function. Both of them can also be used to save copying of big objects when passed as arguments to functions or returned from functions, to get efficiency gain. Despite above similarities, there are following differences between references and pointers.

References are less powerful than pointers

1) 참조가 생성되면 나중에 다른 객체를 참조 할 수 없습니다. 다시 장착 할 수 없습니다. 이것은 종종 포인터로 수행됩니다.

2) 참조는 NULL 일 수 없습니다. 포인터는 종종 유효한 것으로 가리키고 있지 않음을 나타 내기 위해 NULL이됩니다.

3) 참조가 선언되면 초기화되어야합니다. 포인터에는 이러한 제한이 없습니다.

Due to the above limitations, references in C++ cannot be used for implementing data structures like Linked List, Tree, etc. In Java, references don't have above restrictions, and can be used to implement all data structures. References being more powerful in Java, is the main reason Java doesn't need pointers.

참조가 더 안전하고 사용하기 쉽습니다.

1) 안전 : 참조를 초기화해야하므로 야생 포인터와 같은 야생 참조가 존재하지 않을 수 있습니다. 유효한 위치를 참조하지 않는 참조를 가질 수도 있습니다.

2) 사용하기 쉬움 : 참조에 값에 액세스 할 때 역 참조 연산자가 필요하지 않습니다. 일반 변수처럼 사용할 수 있습니다. '&'연산자는 선언시에만 필요합니다. 또한 멤버에 액세스하는 데 화살표 연산자 (->)가 필요한 포인터와 달리 객체 참조의 멤버는 도트 연산자 ( '.')로 액세스 할 수 있습니다.

Together with the above reasons, there are few places like copy constructor argument where pointer cannot be used. Reference must be used pass the argument in copy constructor. Similarly references must be used for overloading some operators like ++ .




A reference is an alias for another variable whereas a pointer holds the memory address of a variable. References are generally used as function parameters so that the passed object is not the copy but the object itself.

    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. 



Another interesting use of references is to supply a default argument of a user-defined type:

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;
}

The default flavor uses the 'bind const reference to a temporary' aspect of references.




Another difference is that you can have pointers to a void type (and it means pointer to anything) but references to void are forbidden.

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

I can't say I'm really happy with this particular difference. I would much prefer it would be allowed with the meaning reference to anything with an address and otherwise the same behavior for references. It would allow to define some equivalents of C library functions like memcpy using references.




Related