C # 참조와 포인터의 차이점은 무엇입니까?


85

C # 참조와 포인터의 차이점을 잘 이해하지 못합니다. 둘 다 기억 속의 장소를 가리 킵니다. 그렇죠? 내가 알아낼 수있는 유일한 차이점은 포인터가 똑똑하지 않고 힙에있는 어떤 것도 가리킬 수없고 가비지 수집에서 제외되며 구조체 또는 기본 유형 만 참조 할 수 있다는 것입니다.

내가 묻는 이유 중 하나는 좋은 프로그래머가되기 위해 사람들이 포인터를 잘 이해해야한다는 인식이 있기 때문입니다. 더 높은 수준의 언어를 배우는 많은 사람들은 이것을 놓치고 따라서 이러한 약점을 가지고 있습니다.

포인터에 대해 그렇게 복잡한 것을 이해하지 못합니까? 그것은 기본적으로 기억의 장소에 대한 참조 일 뿐이지 않습니까? 위치를 반환하고 해당 위치의 개체와 직접 상호 작용할 수 있습니까?

중요한 포인트를 놓친 적이 있습니까?


1
짧은 대답은 그렇습니다. 상당히 중요한 것을 놓쳤습니다. 이것이 "... 사람들이 포인터를 이해해야한다는 인식"의 이유입니다. 힌트 : C #은 유일한 언어가 아닙니다.
jdigital

답변:


50

C # 참조는 가비지 수집기에 의해 재배치 될 수 있으며 재배치되지만 일반 포인터는 정적입니다. 이것이 우리 fixed가 배열 요소에 대한 포인터를 획득 할 때 키워드가 이동되는 것을 방지하기 위해 사용하는 이유입니다.

편집 : 개념적으로, 예. 그들은 다소 동일합니다.


C # 참조가 GC에 의해 참조 된 개체를 이동하지 못하도록하는 다른 명령이 있습니까?
Richard

죄송합니다. 게시물이 포인터를 언급했기 때문에 다른 것이라고 생각했습니다.
Richard

예, GCHandle.Alloc 또는 Marshal.AllocHGlobal (고정 이상)
ctacke

C ++ / CLI의 C #, pin_ptr에서 수정 됨
Mehrdad Afshari

Marshal.AllocHGlobal은 관리되는 힙에 메모리를 전혀 할당하지 않으며 당연히 가비지 수집의 대상이 아닙니다.
Mehrdad Afshari

130

포인터와 참조 사이에는 약간의 차이가 있지만 매우 중요한 차이가 있습니다. 포인터는 메모리의 한 위치를 가리키고 참조는 메모리의 개체를 가리 킵니다. 포인터가 가리키는 메모리의 정확성을 보장 할 수 없다는 점에서 포인터는 "타입 안전"이 아닙니다.

예를 들어 다음 코드를 살펴보십시오.

int* p1 = GetAPointer();

이것은 GetAPointer가 int *와 호환되는 형식을 반환해야한다는 점에서 형식 안전입니다. 그러나 * p1이 실제로 int를 가리킬 것이라는 보장은 아직 없습니다. char, double 또는 임의 메모리에 대한 포인터 일 수 있습니다.

그러나 참조는 특정 개체를 가리 킵니다. 객체는 메모리에서 이동할 수 있지만 참조는 무효화 될 수 없습니다 (안전하지 않은 코드를 사용하지 않는 한). 이 점에서 참조는 포인터보다 훨씬 안전합니다.

string str = GetAString();

이 경우 str은 두 가지 상태 중 하나를 갖습니다. 1) 객체를 가리 키지 않으므로 null이거나 2) 유효한 문자열을 가리 킵니다. 그게 다야. CLR은이를 보장합니다. 그것은 포인터를 위해 할 수 없으며 그렇지 않을 것입니다.


14
훌륭한 설명.
iTayb 2010 년

13

참조는 "추상"포인터입니다. 참조로 산술을 할 수없고 그 값으로 저수준 트릭을 할 수 없습니다.


8

참조와 포인터의 주요 차이점은 포인터가 포인터로 활발하게 사용되는 경우에만 내용이 중요한 비트 모음 인 반면 참조는 비트 집합뿐만 아니라 일부 메타 데이터를 캡슐화한다는 것입니다. 그 존재를 알린 기본 프레임 워크. 포인터가 메모리에있는 일부 개체에 대해 존재하고 해당 개체가 삭제되었지만 포인터가 지워지지 않은 경우 포인터가 가리키는 메모리에 액세스하려고 시도하지 않는 한 또는 포인터가 계속 존재해도 아무런 해를 끼치 지 않습니다. 포인터를 사용하려는 시도가 없으면 그 존재를 신경 쓰지 않습니다. 반대로 .NET 또는 JVM과 같은 참조 기반 프레임 워크에서는 시스템이 항상 존재하는 모든 객체 참조를 식별 할 수 있어야하며 존재하는 모든 객체 참조는 항상null 또는 적절한 유형의 개체를 식별합니다.

각 개체 참조는 실제로 두 종류의 정보를 캡슐화합니다. (1) 식별하는 개체의 필드 내용 및 (2) 동일한 개체에 대한 다른 참조 집합입니다. 시스템이 객체에 존재하는 모든 참조를 신속하게 식별 할 수있는 메커니즘은 없지만 객체에 존재하는 다른 참조 집합이 참조로 캡슐화되는 가장 중요한 것이 될 수 있습니다 (특히 다음과 같은 경우에 해당). 유형의 Object사물은 잠금 토큰과 같은 것으로 사용됩니다.) 시스템은에서 사용하기 위해 각 객체에 대해 몇 비트의 데이터를 유지하지만 객체에 GetHashCode존재하는 참조 집합을 넘어서는 실제 ID가 없습니다. X객체에 대한 유일한 현존 참조를 보유하는 경우X동일한 필드 내용을 가진 새 객체를 참조하면에서 반환 된 비트를 변경하는 것 외에는 식별 가능한 효과가 없으며 GetHashCode()그 효과조차 보장되지 않습니다.


5

포인터는 메모리 주소 공간의 위치를 ​​가리 킵니다. 참조는 데이터 구조를 가리 킵니다. 데이터 구조는 모두 가비지 수집기 (메모리 공간 압축을 위해)에 의해 항상 (그렇게 자주는 아니고 가끔씩) 이동했습니다. 또한 언급했듯이 참조가없는 데이터 구조는 잠시 후에 가비지 수집됩니다.

또한 포인터는 안전하지 않은 컨텍스트에서만 사용할 수 있습니다.


5

개발자가 포인터 의 개념 , 즉 간접 을 이해하는 것이 중요하다고 생각합니다 . 그렇다고 반드시 포인터를 사용해야하는 것은 아닙니다. 그것은 이해하는 것도 중요 기준의 개념 로부터 다릅니다 포인터의 개념 , 참조의 구현은 거의 항상 비록 단지 미묘하지만 입니다 포인터.

즉, 참조를 보유하는 변수는 객체에 대한 포인터를 보유하는 포인터 크기의 메모리 블록입니다. 그러나이 변수는 포인터 변수를 사용할 수있는 것과 같은 방식으로 사용할 수 없습니다. C # (및 C 및 C ++, ...)에서 포인터는 배열처럼 인덱싱 될 수 있지만 참조는 그렇지 않습니다. C #에서 참조는 가비지 수집기에 의해 추적되지만 포인터는 추적 될 수 없습니다. C ++에서는 포인터를 다시 할당 할 수 있지만 참조는 할당 할 수 없습니다. 구문 적으로나 의미 적으로 포인터와 참조는 상당히 다르지만 기계적으로는 동일합니다.


배열은 흥미롭게 들립니다. 기본적으로 참조로 이것을 수행 할 수없는 동안 배열처럼 메모리 위치를 오프셋하도록 포인터를 지시 할 수 있다는 것입니다. 그것이 언제 유용 할 지 생각할 수 없지만 그다지 흥미롭지 않습니다.
Richard

p가 int * (int에 대한 포인터)이면 (p + 1)은 p + 4 바이트 (int의 크기)로 식별되는 주소입니다. 그리고 p [1]은 * (p + 1)과 같습니다 (즉, p를 지나서 4 바이트 주소를 "역 참조"). 반대로 배열 참조 (C #)를 사용하면 [] 연산자가 함수 호출을 수행합니다.
P Daddy

5

먼저 sematics에서 "Pointer"를 정의해야한다고 생각합니다. 고정 으로 안전하지 않은 코드에서 만들 수있는 포인터를 의미 합니까? 네이티브 호출 또는 Marshal.AllocHGlobal 에서 얻은 IntPtr 을 의미 합니까? GCHandle 을 의미 합니까? 클래스, 숫자, 구조체 등 모든 것이 본질적으로 동일한 것입니다. 무언가가 저장된 메모리 주소의 표현입니다. 그리고 기록을 위해 그들은 확실히 힙에있을 수 있습니다.

포인터 (위의 모든 버전)는 고정 된 항목입니다. GC는 해당 주소에 무엇이 있는지 알지 못하므로 개체의 메모리 나 수명을 관리 할 수 ​​없습니다. 즉, 가비지 수집 시스템의 모든 이점을 잃게됩니다. 개체 메모리를 수동으로 관리해야하며 누수가 발생할 가능성이 있습니다.

반면에 참조는 GC가 알고있는 "관리 포인터"입니다. 여전히 객체의 주소이지만 이제 GC는 대상의 세부 정보를 알고 있으므로 이동, 압축, 마무리, 폐기 및 관리 환경이 수행하는 기타 모든 멋진 작업을 수행 할 수 있습니다.

실제로 가장 큰 차이점은 사용 방법과 이유입니다. 관리되는 언어의 대부분의 경우 개체 참조를 사용합니다. 포인터는 interop을 수행하는 데 유용하며 매우 빠른 작업이 거의 필요하지 않습니다.

편집 : 실제로 여기 에 관리 코드에서 "포인터"를 사용할 수 있는 좋은 예가 있습니다.이 경우에는 GCHandle이지만 AllocHGlobal을 사용하거나 고정 된 바이트 배열 또는 구조체를 사용하여 똑같은 작업을 수행 할 수 있습니다. 나는 GCHandle이 나에게 더 ".NET"이라고 느끼기 때문에 선호하는 경향이 있습니다.


여기에 "관리 포인터"라고 말하면 안되는 사소한 문제입니다. 겁을주는 따옴표를 사용하더라도 IL의 객체 참조와는 상당히 다릅니다. C ++ / CLI에 관리 포인터에 대한 구문이 있지만 일반적으로 C #에서 액세스 할 수 없습니다. IL에서는 (ie) ldloca 및 ldarga 명령을 사용하여 얻습니다.
Glenn Slayden 2011

5

포인터는 응용 프로그램의 주소 공간에있는 모든 바이트를 가리킬 수 있습니다. 참조는 .NET 환경에 의해 엄격하게 제한되고 제어 및 관리됩니다.


1

포인터를 다소 복잡하게 만드는 것은 포인터가 아닌 포인터로 할 수있는 일입니다. 그리고 포인터에 대한 포인터에 대한 포인터가있을 때. 정말 재미있어지기 시작할 때입니다.


1

포인터에 대한 참조의 가장 큰 이점 중 하나는 더 큰 단순성과 가독성입니다. 항상 그렇듯이 무언가를 단순화하면 사용하기가 더 쉬워 지지만 유연성과 제어를 희생시키면서 낮은 수준의 물건을 얻을 수 있습니다 (다른 사람들이 언급했듯이).

포인터는 종종 '추악'하다는 비판을받습니다.

class* myClass = new class();

이제 사용할 때마다 먼저 역 참조해야합니다.

myClass->Method() or (*myClass).Method()

약간의 가독성을 잃고 복잡성을 추가 함에도 불구하고 사람들은 여전히 ​​포인터를 매개 변수로 자주 사용하여 값으로 전달하는 대신 실제 개체를 수정할 수 있고 거대한 개체를 복사 할 필요가없는 성능 향상을 위해 여전히 필요했습니다.

나에게 이것이 포인터와 동일한 이점을 제공하지만 모든 포인터 구문이없는 참조가 처음에 '태어난'이유입니다. 이제 실제 객체 (그 값뿐만 아니라)를 전달할 수 있으며 객체와 상호 작용하는 더 읽기 쉽고 일반적인 방법이 있습니다.

MyMethod(&type parameter)
{
   parameter.DoThis()
   parameter.DoThat()
}

C ++ 참조는 C # / Java 참조와 다른 점에서 값을 할당하면 다시 할당 할 수 없으며 선언 할 때 할당해야합니다. 이것은 const 포인터 (다른 개체를 다시 가리킬 수없는 포인터)를 사용하는 것과 동일합니다.

Java와 C #은 수년에 걸쳐 C / C ++에 축적 된 많은 혼란을 정리 한 매우 높은 수준의 최신 언어이며 포인터는 분명히 '정리'해야하는 것들 중 하나였습니다.

포인터를 아는 것에 대한 귀하의 의견이 당신을 더 강력한 프로그래머로 만드는 한, 이것은 대부분의 경우에 해당됩니다. 당신이 모르는 사이에 그냥 사용하는 것과는 반대로 어떤 것이 '어떻게'작동하는지 안다면 나는 이것이 종종 당신에게 우위를 줄 수 있다고 말할 것입니다. 항상 변하는 가장자리의 양. 결국 구현 방법을 모른 채 무언가를 사용하는 것은 OOP 및 인터페이스의 많은 아름다움 중 하나입니다.

이 특정 예에서 포인터에 대해 아는 것이 참조에 도움이되는 것은 무엇입니까? C # 참조가 개체 자체가 아니라 개체를 가리키는 것을 이해하는 것은 매우 중요한 개념입니다.

# 1 : 값으로 전달하지 않습니다 . 포인터를 사용할 때 포인터가 주소 만 보유하고 있다는 사실을 알 수 있습니다. 그게 다입니다. 변수 자체는 거의 비어 있으므로 인수로 전달하는 것이 좋습니다. 성능 향상 외에도 실제 개체로 작업하므로 변경 사항이 일시적이 아닙니다.

# 2 : 다형성 / 인터페이스 인터페이스 유형 인 참조가 있고 객체를 가리키는 경우 객체가 더 많은 능력을 가질 수 있더라도 해당 인터페이스의 메서드 만 호출 할 수 있습니다. 객체는 동일한 메서드를 다르게 구현할 수도 있습니다.

이러한 개념을 잘 이해한다면 포인터를 사용하지 않아서 너무 많이 놓치고 있다고 생각하지 않습니다. C ++는 가끔 손을 더럽히는 것이 좋기 때문에 프로그래밍을 배우는 언어로 자주 사용됩니다. 또한 낮은 수준의 측면으로 작업하면 현대 언어의 편안함을 높이 평가할 수 있습니다. 저는 C ++로 시작했고 이제는 C # 프로그래머이며 원시 포인터로 작업하면서 내부에서 진행되는 작업을 더 잘 이해하는 데 도움이 된 것 같습니다.

모든 사람이 포인터로 시작할 필요는 없다고 생각하지만, 중요한 것은 값 유형 대신 참조가 사용되는 이유를 이해하고이를 이해하는 가장 좋은 방법은 조상 인 포인터를 보는 것입니다.


1
개인적 으로을 사용 .하는 대부분의 장소에서 C #이 더 나은 언어 였을 것이라고 생각 ->하지만 foo.bar(123)정적 메서드 호출과 동의어였습니다 fooClass.bar(ref foo, 123). 그것은 다음과 같은 것을 허용했을 것입니다 myString.Append("George"); [이는 수정 것이다 변수 myString ] 사이에 더 의미 명백한 차이를 제작 myStruct.field = 3;하고 myClassObject->field = 3;.
supercat 2013
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.