[c] 메모리 주소가 아닌 경우 C 포인터는 정확히 무엇입니까?

C에 대한 평판이 좋은 소스에서 &운영자를 논의한 후 다음 정보가 제공됩니다 .

… 주소가 무엇인지 모르는 사람들과 혼동하기 때문에 용어 [주소] 가 남아 있다는 것은 불행한 일입니다 . .

내가 읽은 다른 자료 (동일하게 평판 좋은 출처에서)는 항상 포인터와 &연산자를 메모리 주소를 제공 한다고 언급했습니다 . 나는 그 문제의 실제 성을 계속 찾고 싶어하지만 평판이 좋은 출처가 동의하지 않을 때는 어려운 일이다.

메모리 포인터가 아닌 경우 포인터가 정확히 무엇 입니까?

추신

저자는 나중에 다음과 같이 말합니다. … ‘주소’라는 용어를 계속 사용할 것입니다. 다른 용어 를 발명하는 것이 더 나빠질 수 있기 때문 입니다.



답변

C 표준은 포인터가 내부적으로 무엇이고 어떻게 내부적으로 작동하는지 정의하지 않습니다. 이것은 C가 컴파일되거나 해석되는 언어로 구현 될 수있는 플랫폼의 수를 제한하지 않도록 의도 된 것입니다.

포인터 값은 일종의 ID 또는 핸들 또는 여러 ID의 조합 (예 : x86 세그먼트 및 오프셋에 대한 안녕하세요) 일 수 있으며 반드시 실제 메모리 주소는 아닙니다. 이 ID는 고정 크기 텍스트 문자열 일 수도 있습니다. 비 주소 표현은 C 인터프리터에게 특히 유용 할 수 있습니다.


답변

나는 당신의 출처에 대해 잘 모르겠지만, 당신이 묘사하는 언어의 유형은 C 표준에서 나옵니다.

6.5.3.2 주소 및 간접 연산자
[…]
3. 단항 및 연산자는 피연산자의 주소를 생성합니다. […]

그래서 … 네, 포인터는 메모리 주소를 가리 킵니다. 적어도 그것이 C 표준이 의미하는 바입니다.

좀 더 명확하게 말하면 포인터는 일부 주소 을 보유하는 변수 입니다. 단항 연산자 를 사용하여 객체의 주소 (포인터에 저장 될 수 있음)가 반환됩니다 .&

주소에 “42 Wallaby Way, Sydney”주소를 변수에 저장할 수 있습니다 (그리고 해당 변수는 일종의 “포인터”가 될 수 있지만 메모리 주소가 아니기 때문에 “포인터”라고 부르는 것이 아닙니다). 컴퓨터에는 메모리 버킷에 대한 주소가 있습니다. 포인터는 주소 값을 저장합니다 (포인터는 주소 인 “42 Wallaby Way, Sydney”를 저장합니다).

편집 : Alexey Frunze의 의견을 넓히고 싶습니다.

포인터가 정확히 무엇입니까? C 표준을 보자 :

6.2.5 유형
[…]
(20)은 […]
포인터 유형은 호출 함수 타입 또는 개체 유형에서 유도 될 수있다 참조 타입 . 포인터 유형은 값이 참조 된 유형의 엔티티에 대한 참조를 제공하는 오브젝트를 설명합니다. 참조 된 유형 T에서 파생 된 포인터 유형을 때때로 ”포인터에 대한 포인터 ”라고합니다. 참조 된 유형으로부터 포인터 유형을 구성하는 것을“포인터 유형 도출 ”이라고합니다. 포인터 유형은 완전한 객체 유형입니다.

기본적으로 포인터는 일부 객체 또는 함수에 대한 참조를 제공하는 값을 저장합니다. 거의. 포인터는 일부 객체 또는 함수에 대한 참조를 제공하는 값을 저장하려고하지만 항상 그런 것은 아닙니다 .

6.3.2.3 포인터
[…]
5. 정수는 모든 포인터 유형으로 변환 될 수 있습니다. 이전에 지정된 경우를 제외하고 결과는 구현에 따라 정의되고 올바르게 정렬되지 않았으며 참조 된 유형의 엔티티를 가리 키지 않을 수 있으며 트랩 표현 일 수 있습니다.

위의 인용문은 정수를 포인터로 바꿀 수 있다고 말합니다. 그렇게하면 (즉, 객체 또는 함수에 대한 특정 참조 대신 포인터에 정수 값을 입력하면) 포인터가 “참조 유형의 엔티티를 가리 키지 않을 수 있습니다”(즉, 객체 또는 함수에 대한 참조). 그것은 우리에게 다른 것을 제공 할 수도 있습니다. 그리고 이것은 포인터에 어떤 종류의 핸들이나 ID를 붙일 수있는 곳입니다 (즉, 포인터가 객체를 가리 키지 않습니다. 무언가를 나타내는 값을 저장하지만 해당 값은 주소가 아닐 수 있습니다).

Alexey Frunze가 말했듯이 포인터가 객체 또는 함수의 주소를 저장하지 않을 수 있습니다. 포인터가 대신 일종의 “핸들”또는 ID를 저장하는 것이 가능할 수 있으며, 포인터에 임의의 정수 값을 할당하여이를 수행 할 수 있습니다. 이 핸들 또는 ID가 나타내는 것은 시스템 / 환경 / 컨텍스트에 따라 다릅니다. 시스템 / 구현이 가치를 이해할 수있는 한, 당신은 좋은 모양입니다 (그러나 그것은 특정 가치와 특정 시스템 / 구현에 달려 있습니다).

일반적으로 포인터는 객체 또는 함수에 대한 주소를 저장합니다. 실제 주소 (객체 또는 함수에 대한)를 저장하지 않으면 결과는 구현이 정의됩니다 (정확하게 발생하는 포인터와 현재 포인터가 나타내는 것은 시스템 및 구현에 따라 다르므로 핸들 또는 ID 일 수 있음) 특정 시스템이지만 다른 시스템에서 동일한 코드 / 값을 사용하면 프로그램이 중단 될 수 있습니다).

결국 내가 생각했던 것보다 길어졌습니다.


답변

포인터 대 변수

이 사진에서,

pointer_p는 0x12345에 위치한 포인터이며 0x34567에서 variable_v 변수를 가리 킵니다.


답변

포인터를 주소로 생각하는 것은 근사치 입니다. 모든 근사와 마찬가지로 때로는 유용 할 정도로 충분하지만 정확하지도 않아서 문제가 발생할 수 있습니다.

포인터는 객체를 찾을 위치를 나타내는 점에서 주소와 같습니다. 이 비유의 즉각적인 한계 중 하나는 모든 포인터가 실제로 주소를 포함하지는 않는다는 것입니다. NULL주소가 아닌 포인터입니다. 포인터 변수의 내용은 실제로 다음 세 가지 중 하나 일 수 있습니다.

  • 역 참조 될 수있는 객체 의 주소 (주소p포함 된 경우 x표현식 *p의 값은 x) 와 동일합니다 .
  • NULL 포인터 그중 NULL한 예이고;
  • 유효하지 않은 컨텐츠는 객체를 가리 키지 않습니다 ( p유효한 값을 보유하지 않으면 *p프로그램을 크래시하면서 상당히 일반적인 가능성으로 무언가를 수행 할 수 있음).

또한 포인터 (유효하고 null이 아닌 경우) 에 주소 가 포함되어 있다고 말하는 것이 더 정확 합니다. 포인터는 객체를 찾을 위치를 나타내지 만 더 많은 정보가 있습니다.

특히 포인터에는 유형이 있습니다. 대부분의 플랫폼에서 포인터 유형은 런타임에 영향을 미치지 않지만 컴파일시 유형을 넘어서는 영향을 미칩니다. ( )에 p대한 포인터 인 경우 바이트 뒤 의 정수 를 가리 킵니다 ( 여전히 유효한 포인터 라고 가정 ). 경우 에 대한 포인터 와 같은 주소로 그 점 ( ), 다음 과 같은 주소가 아닌 . 포인터를 주소로 생각하면“다음 주소”가 같은 위치에 대한 다른 포인터마다 다른 것은 매우 직관적이지 않습니다.intint *p;p + 1sizeof(int)pp + 1qcharpchar *q = p;q + 1p + 1

일부 환경에서는 메모리에서 동일한 위치를 가리키는 다른 표현 (메모리의 다른 비트 패턴)을 가진 여러 포인터 값을 가질 수 있습니다. 이것을 같은 주소를 가진 다른 포인터 또는 같은 위치에 대한 다른 주소로 생각할 수 있습니다.이 경우에는 은유가 명확하지 않습니다. ==두 피연산자가 그래서 당신이 가질 수있는 이러한 환경에서, 같은 위치를 가리키는 여부 연산자는 항상 당신을 알려줍니다 p == q에도 불구 p하고 q서로 다른 비트 패턴을 가지고있다.

포인터가 유형 또는 권한 정보와 같이 주소 이외의 다른 정보를 전달하는 환경도 있습니다. 프로그래머로서의 삶을 쉽게 접할 수 있습니다.

다른 종류의 포인터가 다른 표현을 갖는 환경이 있습니다. 이를 다른 표현을 갖는 다른 종류의 주소로 생각할 수 있습니다. 예를 들어, 일부 아키텍처에는 바이트 포인터와 단어 포인터 또는 객체 포인터와 함수 포인터가 있습니다.

대체로 포인터를 주소로 생각하는 것이 당신이 명심하는 한 그렇게 나쁘지 않습니다.

  • 주소가 아닌 유효하고 널이 아닌 포인터입니다.
  • 동일한 위치에 대해 여러 개의 주소를 가질 수 있습니다.
  • 주소에 대해 산술을 할 수 없으며 순서가 없습니다.
  • 포인터는 타입 정보도 가지고 있습니다.

다른 방향으로 나아가는 것은 훨씬 더 번거 롭습니다. 주소처럼 보이는 모든 것이 포인터가 될 수있는 것은 아닙니다 . 어딘가에있는 포인터는 정수로 읽을 수있는 비트 패턴으로 표시되며이 정수는 주소라고 말할 수 있습니다. 그러나 다른 방법으로는 모든 정수가 포인터가 아닙니다.

먼저 몇 가지 잘 알려진 제한 사항이 있습니다. 예를 들어, 프로그램의 주소 공간 외부의 위치를 ​​지정하는 정수는 유효한 포인터가 될 수 없습니다. 잘못 정렬 된 주소는 정렬이 필요한 데이터 유형에 대한 유효한 포인터를 만들지 않습니다. 예를 들어, int4 바이트 정렬이 필요한 플랫폼에서 0x7654321은 유효한 int*값이 될 수 없습니다 .

그러나 포인터를 정수로 만들면 문제가 발생하기 때문에 그 이상입니다. 이 문제의 큰 부분은 대부분의 프로그래머가 기대하는 것보다 컴파일러 최적화가 미시 최적화에 훨씬 뛰어나 프로그램의 작동 방식에 대한 정신 모델이 매우 잘못되었다는 것입니다. 주소가 동일한 포인터를 가지고 있다고해서 이것이 동일한 것은 아닙니다. 예를 들어 다음 스 니펫을 고려하십시오.

unsigned int x = 0;
unsigned short *p = (unsigned short*)&x;
p[0] = 1;
printf("%u = %u\n", x, *p);

sizeof(int)==4그리고 sizeof(short)==2, 그리고이 곳에서 밀링 머신에서 1 = 1?(little-endian) 또는 65536 = 1?(big-endian)을 인쇄 할 것으로 예상 할 수 있습니다 . 그러나 GCC 4.4가 설치된 64 비트 Linux PC에서

$ c99 -O2 -Wall a.c && ./a.out 
a.c: In function main’:
a.c:6: warning: dereferencing pointer p does break strict-aliasing rules
a.c:5: note: initialized from here
0 = 1?

GCC는 이 간단한 예제에서 무엇이 잘못되었는지 경고하기에 충분히 친절합니다 . 더 복잡한 예제에서는 컴파일러가 인식하지 못할 수 있습니다. p와 다른 유형이 있으므로 &x어떤 p점을 변경하면 어떤 점이 영향을받을 수 없는지 &x(일부 잘 정의 된 예외 제외). 따라서 컴파일러는 x레지스터에 값을 유지 하고이 레지스터를 *p변경으로 업데이트하지 않아도 됩니다. 프로그램은 동일한 주소에 대한 두 개의 포인터를 역 참조하고 두 개의 다른 값을 얻습니다!

이 예제의 교훈은 C 언어의 정확한 규칙을 유지하는 한 (널이 아닌 유효) 포인터를 주소로 생각하는 것이 좋습니다. 동전의 반대 측면은 C 언어의 규칙이 복잡하고 후드 아래에서 무슨 일이 발생하는지 알지 못하면 직관적 인 느낌을 얻는 것이 어렵다는 것입니다. 또한“이국적인”프로세서 아키텍처와 컴파일러 최적화를 지원하기 위해 포인터와 주소 사이의 연결이 다소 느슨해졌습니다.

따라서 포인터가 주소를 이해의 첫 단계로 생각하지만 너무 직관을 따르지 마십시오.


답변

포인터는 주소 자체가 아니라 메모리 주소를 보유하는 변수입니다. 그러나 포인터를 역 참조하고 메모리 위치에 액세스 할 수 있습니다.

예를 들면 다음과 같습니다.

int q = 10; /*say q is at address 0x10203040*/
int *p = &q; /*means let p contain the address of q, which is 0x10203040*/
*p = 20; /*set whatever is at the address pointed by "p" as 20*/

그게 다야. 그렇게 간단합니다.

여기에 이미지 설명을 입력하십시오

내가 말하고있는 것을 보여주는 프로그램과 그 결과는 다음과 같습니다.

http://ideone.com/rcSUsb

프로그램:

#include <stdio.h>

int main(int argc, char *argv[])
{
  /* POINTER AS AN ADDRESS */
  int q = 10;
  int *p = &q;

  printf("address of q is %p\n", (void *)&q);
  printf("p contains %p\n", (void *)p);

  p = NULL;
  printf("NULL p now contains %p\n", (void *)p);
  return 0;
}


답변

그 책의 저자가 정확히 무엇을 의미하는지 말하기는 어렵습니다. 포인터에 주소가 포함되어 있는지 여부는 주소를 정의하는 방법과 포인터를 정의하는 방법에 따라 다릅니다.

작성된 모든 답변에서 판단 할 때 일부 사람들은 (1) 주소가 정수 여야하며 (2) 포인터가 사양에서 그렇게 말하지 않은 가상으로 될 필요는 없다고 가정합니다. 이러한 가정을 통해 명확하게 포인터에 주소가 반드시 포함되는 것은 아닙니다.

그러나 (2)는 사실이지만 (1)은 사실 일 필요는 없습니다. 그리고 @는 @CornStalks의 답변에 따라 &가 운영자 의 주소 라고합니다 . 이것은 스펙 작성자가 포인터가 주소를 포함하려고 함을 의미합니까?

포인터에 주소가 있지만 주소가 정수일 필요는 없다고 말할 수 있습니까? 아마도.

나는이 모든 것이 욕설적인 의미 론적 대화라고 생각한다. 실질적으로 말하면 전혀 쓸모가 없습니다. 포인터의 값이 주소가 아닌 방식으로 코드를 생성하는 컴파일러를 생각할 수 있습니까? 그렇다면 무엇입니까? 그것이 내가 생각했던 거죠…

필자는 책의 저자 (포인터가 반드시 주소가 아니라고 주장하는 첫 번째 발췌 부분)가 언급 한 것은 포인터가 고유 유형 정보와 함께 제공된다는 사실입니다.

예를 들어

 int x;
 int* y = &x;
 char* z = &x;

y와 z는 모두 포인터이지만 y + 1과 z + 1은 다릅니다. 그것들이 메모리 주소라면, 그 표현들이 당신에게 같은 가치를 부여하지 않습니까?

그리고 여기에 주소가 마치 마치 슬픔으로 인도하는 것처럼 포인터에 대한 생각 이 있습니다 . 사람들이 포인터를 마치 주소처럼 생각하기 때문에 버그가 작성되었으며 , 이는 보통 슬픔으로 이어진다 .

주소 일 수도 있지만 55555는 포인터가 아닐 수도 있지만 (int *) 55555는 포인터입니다. 55555 + 1 = 55556이지만 (int *) 55555 + 1은 55559 (+/- sizeof (int)의 차이)입니다.


답변

포인터는 메모리 위치를 나타내는 추상화 입니다. 이 인용문은 포인터가 메모리 주소 인 것처럼 생각하는 것이 잘못되었다고 말하지 않으며, 단지 “보통 슬픔으로 이어진다”고 말합니다. 다시 말해, 잘못된 예상을 갖게됩니다.

가장 큰 슬픔의 근원은 포인터 산술이며, 실제로 C의 강점 중 하나입니다. 포인터가 주소 인 경우 포인터 산술은 주소 산술 일 것으로 예상됩니다. 하지만 그렇지 않습니다. 예를 들어, 주소에 10을 추가하면 10 개의 주소 단위로 더 큰 주소가 제공됩니다. 그러나 포인터에 10을 추가하면 포인터가 가리키는 객체 종류의 크기 (및 실제 크기는 아니지만 정렬 경계까지 반올림)의 10 배가됩니다. 함께 int *그 어드레스 유닛 40 (바이트)하여 증분시킨다 10을 추가 32 비트 정수로 통상 아키텍처. 숙련 된 C 프로그래머는이 사실을 알고 있으며 그와 함께 살고 있지만 저자는 분명히 은유에 대한 팬이 아닙니다.

포인터의 내용이 메모리 위치를 나타내는 방법에 대한 추가 질문이 있습니다. 많은 답변에서 설명했듯이 주소가 항상 int (또는 long)는 아닙니다. 일부 아키텍처에서 주소는 “세그먼트”에 오프셋을 더한 값입니다. 포인터는 현재 세그먼트에 대한 오프셋 ( “인근”포인터) 만 포함 할 수 있으며, 그 자체로는 고유 한 메모리 주소가 아닙니다. 그리고 포인터 내용은 하드웨어가 이해하는 것처럼 메모리 주소와 간접적으로 만 관련 될 수 있습니다. 그러나 인용 된 인용문의 저자는 심지어 표현을 언급하지 않았기 때문에, 나는 그들이 생각했던 표현이 아니라 개념적 동등성이라고 생각합니다.