developer tip

포인터에 대한 포인터 대 일반 포인터

optionbox 2020. 10. 20. 07:33
반응형

포인터에 대한 포인터 대 일반 포인터


포인터의 목적은 특정 변수의 주소를 저장하는 것입니다. 다음 코드의 메모리 구조는 다음과 같습니다.

int a = 5;
int *b = &a;

...... 메모리 주소 ...... 값
a ... 0x000002 ................... 5
b ... 0x000010 ..... .............. 0x000002

좋아. 그런 다음 이제 포인터 * b의 주소를 저장한다고 가정합니다. 그런 다음 일반적으로 이중 포인터 ** c를 다음과 같이 정의합니다.

int a = 5;
int *b = &a;
int **c = &b;

그러면 메모리 구조는 다음과 같습니다.

...... 메모리 주소 ...... 값
a ... 0x000002 ................... 5
b ... 0x000010 ..... .............. 0x000002
c ... 0x000020 ................... 0x000010

따라서 ** c는 * b의 주소를 나타냅니다.

이제 제 질문은 왜 이런 유형의 코드가

int a = 5;
int *b = &a;
int *c = &b;

경고를 생성 하시겠습니까?

포인터의 목적이 메모리 주소를 저장하는 것이라면 저장할 주소가 변수, 포인터, 이중 포인터 등을 참조하면 계층 구조가 없어야한다고 생각하므로 아래 유형의 코드는 유효합니다.

int a = 5;
int *b = &a;
int *c = &b;
int *d = &c;
int *e = &d;
int *f = &e;

int a = 5;
int *b = &a;   
int *c = &b;

&b유형이 이므로 경고 가 표시되고 유형 int **의 변수를 초기화하려고합니다 int *. 이 두 유형간에 암시 적 변환이 없으므로 경고가 발생합니다.

더 긴 예제를 사용하기 위해 역 참조를 시도 f하면 컴파일러가 int추가 역 참조를 할 수있는 포인터가 아닌를 제공합니다.

많은 시스템에서 참고 int하고 int*(예를 들면 포인터가 64 비트 길이 일 수있다와 동일한 크기없는 int32 비트 길이). 역 참조 f하고를 얻으면 int값의 절반을 잃고 유효한 포인터로 캐스트 할 수도 없습니다.


포인터의 목적이 메모리 주소를 저장하는 것이라면 저장할 주소가 변수, 포인터, 이중 포인터 등을 참조하면 계층 구조가 없어야한다고 생각합니다.

런타임시 포인터는 주소 만 보유합니다. 그러나 컴파일 타임에는 모든 변수와 관련된 유형도 있습니다. 마찬가지로 다른 사람이 말했다, int*그리고 int**두 개의 서로 다른 호환되지 않는 유형이다.

void*원하는 작업을 수행하는 한 가지 유형 이 있습니다. 주소 만 저장하고 모든 주소를 할당 할 수 있습니다.

int a = 5;
int *b = &a;
void *c = &b;

그러나를 역 참조 void*하려면 '누락 된'유형 정보를 직접 제공해야합니다.

int a2 = **((int**)c);

이제 제 질문은 왜 이런 유형의 코드가

int a = 5; 
int *b = &a; 
int *c = &b; 

경고를 생성 하시겠습니까?

기본으로 돌아 가야합니다.

  • 변수에는 유형이 있습니다.
  • 변수는 값을 보유
  • 포인터는 값
  • 포인터는 변수를 참조
  • 만약 p포인터 값은 다음 *p변수는
  • 경우 v변수는 다음 &v포인터입니다

이제 우리는 귀하의 게시물에서 모든 실수를 찾을 수 있습니다.

이제 포인터의 주소를 저장하고 싶다고 가정합니다. *b

No. *b는 int 유형의 변수입니다. 포인터가 아닙니다. 이 포인터 b인 변수입니다 . A는 변수 값이 정수가.*b

**c의 주소를 나타냅니다 *b.

아니, 아니. 절대적으로하지. 당신 당신이 포인터를 이해하려는 경우 올바르게 이해 할 수 있습니다.

*b변수입니다. 변수의 별칭입니다 a. 변수의 주소는 변수 a의 값입니다 b. **c의 주소를 참조하지 않습니다 a. 오히려 variable별칭 인 변수 a입니다. (그리고 *b.)

올바른 문장은 다음과 같습니다. 변수 c 은의 주소 입니다 b. 또는 동등하게 :의 값 c은를 참조하는 포인터입니다 b.

이것을 어떻게 알 수 있습니까? 기본으로 돌아가십시오. 당신은 그렇게 말했다 c = &b. 그렇다면의 가치는 c무엇입니까? 포인터. 무엇을? b.

기본 규칙 완전히 이해 했는지 확인하십시오 .

이제 변수와 포인터 간의 올바른 관계를 이해 했으므로 코드에서 오류가 발생하는 이유에 대한 질문에 답할 수 있습니다.


C의 타입 시스템은 올바른 경고를 받고 싶고 코드가 전혀 컴파일되기를 원한다면 이것을 요구합니다. 포인터의 깊이 수준이 하나뿐이면 포인터가 포인터를 가리키는 지 실제 정수를 가리키는 지 알 수 없습니다.

유형을 역 참조하면 유형 int**int*이고 마찬가지로 int*유형이 int. 귀하의 제안으로 유형이 모호합니다.

귀하의 예에서 ca int또는 int*다음을 가리키는 지 알 수 없습니다 .

c = rand() % 2 == 0 ? &a : &b;

c가 가리키는 유형은 무엇입니까? 컴파일러는이를 알지 못하므로 다음 행은 수행 할 수 없습니다.

*c;

C에서는 모든 유형이 컴파일 타임에 확인되고 더 이상 필요하지 않기 때문에 컴파일 후 모든 유형 정보가 손실됩니다. 모든 포인터가 포인터에 포함 된 유형에 대한 추가 런타임 정보를 가져야하기 때문에 제안은 실제로 메모리와 시간을 낭비합니다.


포인터는 추가 유형 시맨틱이있는 메모리 주소의 추상화 이며 C 유형과 같은 언어에서 중요합니다.

우선, 거기 보장은 없습니다 int *int **같은 크기 나 표현이 (그들이 현대 데스크탑 아키텍처에서,하지만 당신은 보편적으로 진실되고 그것에 의존 할 수 없다).

둘째, 포인터 산술에서 유형이 중요합니다. p유형 의 포인터 주어지면 T *표현식 p + 1유형 의 다음 객체 주소를 산출합니다 T. 따라서 다음 선언을 가정하십시오.

char  *cp     = 0x1000;
short *sp     = 0x1000;  // assume 16-bit short
int   *ip     = 0x1000;  // assume 32-bit int
long  *lp     = 0x1000;  // assume 64-bit long

표현식 cp + 1은 다음 char객체 의 주소를 제공 합니다 0x1001. 표현식 sp + 1은 다음 short객체 의 주소를 제공 합니다 0x1002. ip + 1우리에게주고 0x1004, 우리 lp + 1에게줍니다 0x1008.

그래서 주어진

int a = 5;
int *b = &a;
int **c = &b;

b + 1우리에게 다음의 주소를 제공 int하고, c + 1우리에게 다음의 주소를 제공 포인터 로를 int.

포인터 유형의 매개 변수에 함수를 쓰려면 포인터 대 포인터가 필요합니다. 다음 코드를 사용하십시오.

void foo( T *p )    
{
  *p = new_value(); // write new value to whatever p points to
}

void bar( void )
{
  T val;
  foo( &val );     // update contents of val
}

이것은 모든 유형T해당됩니다 . 우리가 바꿀 경우 T포인터 유형과 P *코드가된다

void foo( P **p )    
{
  *p = new_value(); // write new value to whatever p points to
}

void bar( void )
{
  P *val;
  foo( &val );     // update contents of val
}

의미 체계는 정확히 동일합니다. 단지 유형 만 다릅니다. 형식 매개 변수 p는 항상 변수 보다 한 단계 더 많은 간접 수준입니다 val.


저장하려는 주소가 변수, 포인터, 이중 포인터를 참조하면 계층 구조가 없어야한다고 생각합니다.

"계층 구조"가 없으면 경고없이 UB를 생성하는 것이 매우 쉬울 것입니다. 그것은 끔찍할 것입니다.

이걸 고려하세요:

char c = 'a';
char* pc = &c;
char** ppc = &pc;
printf("%c\n", **ppc);   // compiles ok and is valid
printf("%c\n", **pc);    // error: invalid type argument of unary ‘*’

컴파일러는 나에게 오류를 제공하므로 내가 잘못한 것을 알 수 있으며 버그를 수정할 수 있습니다.

그러나 다음과 같이 "계층 구조"가 없습니다.

char c = 'a';
char* pc = &c;
char* ppc = &pc;
printf("%c\n", **ppc);   // compiles ok and is valid
printf("%c\n", **pc);    // compiles ok but is invalid

컴파일러는 "계층 구조"가 없기 때문에 오류를 제공 할 수 없습니다.

그러나 줄 :

printf("%c\n", **pc);

실행하면 UB (정의되지 않은 동작)입니다.

먼저 *pc를 읽고 char는 포인터 것처럼, 즉 아마 우리가 단지 1 바이트를 보유에도 불구하고 4 또는 8 바이트를 읽습니다. 그것이 UB입니다.

위의 UB로 인해 프로그램이 충돌하지 않고 일부 잘못된 값을 반환 한 경우 두 번째 단계는 잘못된 값을 역 참조하는 것입니다. 다시 한번 UB.

결론

유형 시스템은 int *, int **, int *** 등을 다른 유형으로 간주하여 버그를 감지하는 데 도움이됩니다.


포인터의 목적이 메모리 주소를 저장하는 것이라면 저장하려는 주소가 변수, 포인터, 이중 포인터 등을 참조하면 계층 구조가 없어야하므로 아래 코드 유형이 유효해야합니다.

나는 여기에 당신의 오해가 있다고 생각합니다. 포인터 자체의 목적은 메모리 주소를 저장하는 것이지만, 포인터는 일반적으로 포인터가 가리키는 위치에서 무엇을 기대해야하는지 알 수 있도록 유형을 가지고 있습니다.

특히 당신과 달리 다른 사람들은 포인터가 가리키는 메모리 내용으로 무엇을해야하는지 알기 위해 이런 종류의 계층 구조를 갖고 싶어합니다.

타입 정보를 첨부하는 것이 C 포인터 시스템의 핵심입니다.

만약 당신이

int a = 5;

&a당신이 얻을 것은 것을 의미한다 int *그래서 당신은 역 참조의 경우에 것을 int다시.

다음 단계로 가져 오면

int *b = &a;
int **c = &b;

&b포인터이기도합니다. 그러나 그 뒤에 무엇이 숨어 있는지 알지 못하고 resp. 그것이 가리키는 것은 쓸모가 없습니다. 그 때문에 포인터를 역 참조하면 원래 형태의 유형을 알 수 있음을 아는 것이 중요합니다 *(&b)이다 int *, 그리고 **(&b)원본입니다 int우리가 함께 작동 값.

상황에 따라 유형의 계층 구조가 없어야한다고 생각하는 경우 void *직접적인 사용성은 상당히 제한적이지만 항상을 사용할 수 있습니다 .


포인터의 목적이 메모리 주소를 저장하는 것이라면 저장하려는 주소가 변수, 포인터, 이중 포인터 등을 참조하면 계층 구조가 없어야하므로 아래 코드 유형이 유효해야합니다.

그것은 기계의 경우에 해당됩니다 (대략 모든 것이 숫자입니다). 그러나 많은 언어에서 변수가 입력되므로 컴파일러가 변수를 올바르게 사용하는지 확인할 수 있습니다 (유형이 변수에 올바른 컨텍스트를 부과 함).

포인터에 대한 포인터와 포인터 (아마도)가 값을 저장하기 위해 동일한 양의 메모리를 사용하는 것은 사실입니다 (int에 대한 포인터와 int에 대한 포인터는 사실이 아닙니다. 주소의 크기는 a의 크기와 관련이 없습니다. 집).

따라서 주소의 주소가 있으면 단순 주소가 아닌 그대로 사용해야합니다. 포인터에 대한 포인터를 단순 포인터로 액세스하면 int 주소를 int 인 것처럼 조작 할 수 있기 때문입니다. , 그렇지 않습니다 (다른 것없이 int를 대체하고 위험을 확인해야합니다). 이 모든 것이 숫자이기 때문에 혼란 스러울 수 있지만 일상 생활에서는 그렇지 않습니다. 저는 개인적으로 1 달러와 1 마리에 큰 차이를 만듭니다. 개와 $는 유형입니다. 그들로 무엇을 할 수 있는지 알고 있습니다.

어셈블리로 프로그래밍하고 원하는 것을 만들 수 있지만, 원하는 것을 거의 할 수 있기 때문에, 특히 이상한 일을 할 수 있기 때문에 얼마나 위험한지 관찰 할 수 있습니다. 예, 주소 값을 수정하는 것은 위험합니다. 거리로 표현 된 주소 (1200 memory street (address))에서 무언가를 배달해야하는 자율 주행 자동차가 있고 거리 주택이 100ft (1221은 유효하지 않은 주소)로 분리되어 있다고 가정 해 보겠습니다. 원하는대로 정수로 주소를 조작 할 수 있다면 1223에 배달을 시도하고 포장 중간에 패킷을 놓을 수 있습니다.

또 다른 예로는 집, 집 주소, 해당 주소의 주소록에있는 항목 번호가 있습니다. 이 세 가지 모두 다른 개념, 다른 유형 ...


다양한 유형이 있습니다. 그럴만 한 이유가 있습니다.

데…

int a = 5;
int *b = &a;
int **c = &b;

… 표현식 …

*b * 5

… 유효하지만…

*c * 5

말이 안 돼.

큰 거래는 아닌 방법 하지만, 포인터 또는 포인터 - 투 - 포인터가 저장되는 것과 그들이 참조하십시오.


C 언어는 강력한 형식입니다. 이는 모든 주소에 대해 컴파일러에게 해당 주소의 값을 해석하는 방법을 알려주는 유형 이 있음을 의미합니다 .

귀하의 예에서 :

int a = 5;
int *b = &a;

의 유형은 a이고 int유형은 b입니다 int *( "포인터"로 읽음 int). 귀하의 예를 사용하면 메모리에는 다음이 포함됩니다.

..... memory address ...... value ........ type
a ... 0x00000002 .......... 5 ............ int
b ... 0x00000010 .......... 0x00000002 ... int*

The type is not actually stored in memory, it's just that the compiler knows that, when you read a you'll find an int, and when you read b you'll find the address of a place where you can find an int.

In your second example:

int a = 5;
int *b = &a;
int **c = &b;

The type of c is int **, read as "pointer to pointer to int". It means that, for the compiler:

  • c is a pointer;
  • when you read c, you get the address of another pointer;
  • when you read that other pointer, you get the address of an int.

That is,

  • c is a pointer (int **);
  • *c is also a pointer (int *);
  • **c is an int.

And the memory would contain:

..... memory address ...... value ........ type
a ... 0x00000002 .......... 5 ............ int
b ... 0x00000010 .......... 0x00000002 ... int*
c ... 0x00000020 .......... 0x00000010 ... int**

Since the "type" is not stored together with the value, and a pointer can point to any memory address, the way the compiler knows the type of the value at an address is basically by taking the pointer's type, and removing the rightmost *.


By the way, that's for a common 32-bit architecture. For most 64-bit architectures, you'll have:

..... memory address .............. value ................ type
a ... 0x0000000000000002 .......... 5 .................... int
b ... 0x0000000000000010 .......... 0x0000000000000002 ... int*
c ... 0x0000000000000020 .......... 0x0000000000000010 ... int**

Addresses are now 8 bytes each, while an int is still only 4 bytes. Since the compiler knows the type of each variable, it can easily deal with this difference, and read 8 bytes for a pointer and 4 bytes for the int.


Why does this type of code generate a warning?

int a = 5;
int *b = &a;   
int *c = &b;

The & operator yields a pointer to the object, that is &a is of type int * so assigning (through initialization) it to b which is also of type int * is valid. &b yields a pointer to object b, that is &b is of type pointer to int *, i .e., int **.

C says in the constraints of the assignment operator (which hold for the initialization) that (C11, 6.5.16.1p1): "both operands are pointers to qualified or unqualified versions of compatible types". But in the C definition of what is a compatible type int ** and int * are not compatible types.

So there is a constraint violation in the int *c = &b; initialization which means a diagnostic is required by the compiler.

One of the rationale of the rule here is there is no guarantee by the Standard that the two different pointer types are the same size (except for void * and the character pointer types), that is sizeof (int *) and sizeof (int **) can be different values.


That would be because any pointer T* is actually of type pointer to a T (or address of a T), where T is the pointed-to type. In this case, * can be read as pointer to a(n), and T is the pointed-to type.

int     x; // Holds an integer.
           // Is type "int".
           // Not a pointer; T is nonexistent.
int   *px; // Holds the address of an integer.
           // Is type "pointer to an int".
           // T is: int
int **pxx; // Holds the address of a pointer to an integer.
           // Is type "pointer to a pointer to an int".
           // T is: int*

This is used for dereferencing purposes, where the dereference operator will take a T*, and return a value whose type is T. The return type can be seen as truncating the leftmost "pointer to a(n)", and being whatever's left over.

  *x; // Invalid: x isn't a pointer.
      // Even if a compiler allows it, this is a bad idea.
 *px; // Valid: px is "pointer to int".
      // Return type is: int
      // Truncates leftmost "pointer to" part, and returns an "int".
*pxx; // Valid: pxx is "pointer to pointer to int".
      // Return type is: int*
      // Truncates leftmost "pointer to" part, and returns a "pointer to int".

Note how for each of the above operations, the dereference operator's return type matches the original T* declaration's T type.

This greatly aids both primitive compilers and programmers in parsing a pointer's type: For a compiler, the address-of operator adds a * to the type, the dereference operator removes a * from the type, and any mismatch is an error. For a programmer, the number of *s is a direct indication of how many levels of indirection you're dealing with (int* always points to int, float** always points to float* which in turn always points to float, etc.).


Now, taking this into consideration, there are two major issues with only using a single * regardless of the number of levels of indirection:

  1. The pointer is much more difficult for the compiler to dereference, because it has to refer back to the most recent assignment to determine the level of indirection, and determine the return type appropriately.
  2. The pointer is more difficult for the programmer to understand, because it's easy to lose track of how many layers of indirection there are.

In both cases, the only way to determine the value's actual type would be to backtrack it, forcing you to look somewhere else to find it.

void f(int* pi);

int main() {
    int x;
    int *px = &x;
    int *ppx = &px;
    int *pppx = &ppx;

    f(pppx);
}

// Ten million lines later...

void f(int* pi) {
    int i = *pi; // Well, we're boned.
    // To see what's wrong, see main().
}

This... is a very dangerous problem, and one that is easily solved by having the number of *s directly represent the level of indirection.

참고URL : https://stackoverflow.com/questions/38076981/pointers-to-pointers-vs-normal-pointers

반응형