指向指针的指针和普通指针的指针

时间:2020-12-25 23:23:22

The purpose of a pointer is to save the address of a specific variable. Then the memory structure of following code should look like:

指针的目的是保存特定变量的地址。下面代码的内存结构应该是:

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

...... memory address ...... value
a ... 0x000002 ................... 5
b ... 0x000010 ................... 0x000002

……内存地址……价值……0 x000002 ...................5 b…0 x000010 ...................0 x000002

Okay, fine. Then assume that now I want to save the address of pointer *b. Then we generally define a double pointer, **c, as

好的,很好。然后假设现在我要保存指针*b的地址。然后我们通常定义一个双指针**c, as

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

Then the memory structure looks like:

那么内存结构看起来是:

...... memory address ...... value
a ... 0x000002 ................... 5
b ... 0x000010 ................... 0x000002
c ... 0x000020 ................... 0x000010

……内存地址……价值……0 x000002 ...................5 b…0 x000010 ...................0 x000002 c…0 x000020 ...................0 x000010

So **c refers the address of *b.

所以**c是*b的地址。

Now my question is, why does this type of code,

现在我的问题是,为什么这种类型的代码,

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

generate a warning?

产生一个警告?

If the purpose of pointer is just to save the memory address, I think there should be no hierarchy if the address we are going to save refers to a variable, a pointer, a double pointer, etc., so the below type of code should be valid.

如果指针的目的只是保存内存地址,我认为如果我们要保存的地址指的是一个变量、一个指针、一个双指针等等,那么下面的代码类型应该是有效的。

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

12 个解决方案

#1


91  

In

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

You get a warning because &b is of type int **, and you try to initialize a variable of type int *. There's no implicit conversions between those two types, leading to the warning.

您会得到一个警告,因为b是类型int **,并且您尝试初始化类型为int *的变量。这两种类型之间不存在隐式转换,从而导致警告。

To take the longer example you want to work, if we try to dereference f the compiler will give us an int, not a pointer that we can further dereference.

举一个更长的例子,如果我们尝试去引用f编译器会给我们一个int,而不是一个我们可以去引用的指针。

Also note that on many systems int and int* are not the same size (e.g. a pointer may be 64 bits long and an int 32 bits long). If you dereference f and get an int, you lose half the value, and then you can't even cast it to a valid pointer.

还要注意,在许多系统中,int和int*的大小并不相同(例如,一个指针可能是64位长,一个int 32位长)。如果你去引用f并得到一个int,你失去了一半的值,然后你甚至不能把它转换成一个有效的指针。

#2


54  

If the purpose of pointer is just to save the memory address, I think there should be no hierarchy if the address we are going to save refers variable, pointer, double pointer, ... etc

如果指针的目的仅仅是保存内存地址,我认为如果我们要保存的地址是指变量,指针,双指针,…等

At runtime, yes, a pointer just holds an address. But at compile time there is also a type associated with every variable. As the others have said, int* and int** are two different, incompatible types.

在运行时,是的,指针只包含一个地址。但是在编译时,也有一个类型与每个变量相关联。正如其他人所说,int*和int**是两种不同的、不兼容的类型。

There is one type, void*, that does what you want: It stores only an address, you can assign any address to it:

有一种类型,void*,这就是你想要的:它只存储一个地址,你可以给它分配任何地址:

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

But when you want to dereference a void*, you need to supply the 'missing' type information yourself:

但是,当你想取消一个void*时,你需要自己提供“缺失”类型信息:

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

#3


23  

Now my question is, why does this type of code,

现在我的问题是,为什么这种类型的代码,

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

generate a warning?

产生一个警告?

You need to go back to the fundamentals.

你需要回到基本面。

  • variables have types
  • 变量的类型
  • variables hold values
  • 变量保存值
  • a pointer is a value
  • 指针是一个值
  • a pointer refers to a variable
  • 指针指的是一个变量
  • if p is a pointer value then *p is a variable
  • 如果p是一个指针值,那么*p是一个变量
  • if v is a variable then &v is a pointer
  • 如果v是变量,则&v是指针

And now we can find all the mistakes in your posting.

现在我们可以在你的帖子里找到所有的错误。

Then assume that now I want to save the address of pointer *b

然后假设现在我要保存指针*b的地址

No. *b is a variable of type int. It is not a pointer. b is a variable whose value is a pointer. *b is a variable whose value is an integer.

不。b是int类型的变量,不是指针。b是一个值为指针的变量。*b是一个值为整数的变量。

**c refers to the address of *b.

**c为*b地址。

NO NO NO. Absolutely not. You have to understand this correctly if you are going to understand pointers.

不不不绝对不是。如果你想理解指针,你必须正确地理解它。

*b is a variable; it is an alias for the variable a. The address of variable a is the value of variable b. **c does not refer to the address of a. Rather, it is a variable that is an alias for variable a. (And so is *b.)

* b是一个变量;它是变量a的别名。变量a的地址是变量b的值。**c没有引用a的地址。相反,它是变量a的别名(也就是*b)。

The correct statement is: the value of variable c is the address of b. Or, equivalently: the value of c is a pointer that refers to b.

正确的语句是:变量c的值是b的地址,或者等价地说,c的值是指向b的指针。

How do we know this? Go back to the fundamentals. You said that c = &b. So what is the value of c? A pointer. To what? b.

我们怎么知道的?回到基本面。你说的是c = &b。那么c的值是多少?一个指针。什么?b。

Make sure you fully understand the fundamental rules.

确保你完全理解基本规则。

Now that you hopefully understand the correct relationship between variables and pointers, you should be able to answer your question about why your code gives an error.

既然您希望理解变量和指针之间的正确关系,那么您应该能够回答关于为什么您的代码会出现错误的问题。

#4


20  

The type system of C requires this, if you want to get a correct warning and if you want the code to compile at all. With only one level of depth of pointers you wouldn't know if the pointer is pointing to a pointer or to an actual integer.

C的类型系统需要这样,如果您想获得正确的警告,如果您想编译代码的话。如果指针指向一个指针或一个实际的整数,你就不知道指针的深度。

If you dereference a type int** you know the type you get is int* and similarly if you dereference int* the type is int. With your proposal the type would be ambiguous.

如果你删除类型int**,你知道你得到的类型是int*,同样如果你删除类型int*,类型是int。

Taking from your example, it is impossible to know whether c points to a int or int*:

从你的例子中,不可能知道c是指向int还是int*:

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

What type is c pointing to? The compiler doesn't know that, so this next line is impossible to perform:

c指向什么类型?编译器不知道,所以下一行不可能执行:

*c;

In C all type information is lost after compiling, as every type is checked at compile-time and isn't needed anymore. Your proposal would actually waste memory and time as every pointer would have to have additional runtime information about the types contained in pointers.

在C语言中,所有类型信息在编译后都会丢失,因为每个类型都在编译时被检查,不再需要了。您的提议实际上会浪费内存和时间,因为每个指针都必须有关于指针中包含的类型的额外运行时信息。

#5


17  

Pointers are abstractions of memory addresses with additional type semantics, and in a language like C type matters.

指针是具有附加类型语义的内存地址的抽象,在像C类型这样的语言中很重要。

First of all, there's no guarantee that int * and int ** have the same size or representation (on modern desktop architectures they do, but you can't rely on it being universally true).

首先,不能保证int *和int **具有相同的大小或表示(在现代桌面体系结构中,它们具有相同的大小或表示,但是您不能指望它是通用的)。

Secondly, the type matters for pointer arithmetic. Given a pointer p of type T *, the expression p + 1 yields the address of the next object of type T. So, assume the following declarations:

其次,指针算法的类型很重要。给定一个T *类型的指针p,表达式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

The expression cp + 1 gives us the address of the next char object, which would be 0x1001. The expression sp + 1 gives us the address of the next short object, which would be 0x1002. ip + 1 gives us 0x1004, and lp + 1 gives us 0x1008.

表达式cp + 1给出了下一个char对象的地址,它将是0x1001。表达式sp + 1给出了下一个短对象的地址,即0x1002。ip + 1给出0x1004, lp + 1给出0x1008。

So, given

所以,鉴于

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

b + 1 gives us the address of the next int, and c + 1 gives us the address of the next pointer to int.

b + 1给出了下一个整数的地址,c + 1给出了下一个指针指向int的地址。

Pointer-to-pointers are required if you want a function to write to a parameter of pointer type. Take the following code:

如果希望将函数写入指针类型的参数,则需要指针对指针。下面的代码:

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
}

This is true for any type T. If we replace T with a pointer type P *, the code becomes

对于任何类型T都是如此,如果我们用指针类型P *替换T,那么代码就变成了

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
}

The semantics are exactly the same, it's just the types that are different; the formal parameter p is always one more level of indirection than the variable val.

语义完全相同,只是类型不同;形式参数p总是比变量val多一个层次的间接。

#6


11  

I think there should be no hierarchy if the address we are going to save refers variable, pointer, double pointer

我认为如果我们要保存的地址是指变量,指针,双指针,应该没有层次结构

Without the "hierarchy" it would be very easy to generate UB all over without any warnings - that would be horrible.

如果没有“层次结构”,就很容易在没有任何警告的情况下生成整个UB——这将是可怕的。

Consider this:

考虑一下:

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 ‘*’

The compiler gives me an error and thereby it helps me to know that I have done something wrong and I can correct the bug.

编译器给了我一个错误,因此它帮助我知道我做错了什么,我可以纠正错误。

But without "hierarchy", like:

但没有“等级制度”,如:

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

The compiler can't give any error as there are no "hierarchy".

编译器不能给出任何错误,因为没有“层次结构”。

But when the line:

但当线:

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

executes, it is UB (undefined behavior).

执行时,它是UB(未定义的行为)。

First *pc reads the char as if it was a pointer, i.e. probably reads 4 or 8 bytes even though we only reserved 1 byte. That is UB.

首先*pc像读取指针一样读取字符,即可能读取4或8字节,即使我们只保留1字节。乌兰巴托。

If the program didn't crash due to the UB above but just returned some garbish value, the second step would be to dereference the garbish value. Once again UB.

如果程序不是由于上面的UB而崩溃,而是返回了一些垃圾值,那么第二步就是取消垃圾值。再次乌兰巴托。

Conclusion

结论

The type system helps us to detect bugs by seeing int*, int**, int***, etc as different types.

类型系统通过将int*、int**、int**等视为不同的类型来帮助我们检测bug。

#7


10  

If the purpose of pointer is just to save the memory address, I think there should be no hierarchy if the address we are going to save refers variable, pointer, double pointer, ... etc. so below type of code should be valid.

如果指针的目的仅仅是保存内存地址,我认为如果我们要保存的地址是指变量,指针,双指针,…等等,所以下面的代码类型应该是有效的。

I think here is your misunderstanding: The purpose of the pointer itself is to store the memory address, but a pointer usually as well has a type so that we know what to expect at the place it points to.

我认为这是您的误解:指针本身的目的是存储内存地址,但是指针通常也有一个类型,这样我们就知道在它指向的地方会发生什么。

Especially, unlike you, other people really want to have this kind of hierarchy so as to know what to do with the memory contents which is pointed to by the pointer.

特别地,与你不同,其他人真的希望拥有这种层次结构,以便知道如何处理指针指向的内存内容。

It is the very point of C's pointer system to have type information attached to it.

将类型信息附加到C的指针系统中是最关键的。

If you do

如果你做

int a = 5;

&a implies that what you get is a int * so that if you dereference it is an int again.

与&意味着你得到的是一个int *,所以如果你取消引用,它又是一个int。

Bringing that to the next levels,

把它带到下一个层次,

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

&b is a pointer as well. But without knowing what hides behind it, resp. what it points to, it is useless. It is important to know that dereferencing a pointer reveals the type of the original type, so that *(&b) is an int *, and **(&b) is the original int value we work with.

b也是一个指针。但是不知道它的背后隐藏着什么,重新审视。它所指向的,是无用的。重要的是要知道去引用一个指针会显示原始类型的类型,所以*(&b)是一个int *,而**(&b)是我们使用的原始int值。

If you feel that in your circumstances there should be no hierarchy of types, you can always work with void *, although the direct usability is quite limited.

如果您认为在您的环境中不应该有类型的层次结构,您可以始终使用void *,尽管直接可用性非常有限。

#8


9  

If the purpose of pointer is just to save the memory address, I think there should be no hierarchy if the address we are going to save refers variable, pointer, double pointer, ... etc. so below type of code should be valid.

如果指针的目的仅仅是保存内存地址,我认为如果我们要保存的地址是指变量,指针,双指针,…等等,所以下面的代码类型应该是有效的。

Well that's true for the machine (after all roughly everything is a number). But in many languages variables are typed, means that the compiler can then ensure that you use them correctly (types impose a correct context on variables)

这对机器来说是正确的(毕竟几乎所有东西都是一个数字)。但是在许多语言中,变量是类型化的,这意味着编译器可以确保您正确地使用它们(类型将正确的上下文强加给变量)

It is true that a pointer to pointer and a pointer (probably) use the same amount of memory to store their value (beware this is not true for int and pointer to int, the size of an address is not related to the size of a house).

确实,指针和指针(很可能)使用相同数量的内存来存储它们的值(注意,这对于int和指针是不正确的,地址的大小与房子的大小没有关系)。

So if you have an address of an address you should use as is and not as a simple address because if you access the pointer to pointer as a simple pointer, then you would be able to manipulate an address of int as if it is a int, which is not (replace int without anything else and you should see the danger). You may be confused because all of this are numbers, but in everyday life you don't: I personally make a big difference in $1 and 1 dog. dog and $ are types, you know what you can do with them.

所以如果你有一个地址的地址你应该使用而不是一个简单的地址如果你访问指针的指针作为一个简单的指针,然后你能操纵一个地址的int,好像它是一个整数,这不是(取代int没有别的,你应该看到危险)。你可能会感到困惑,因为所有这些都是数字,但在日常生活中你不会:我个人对1美元和1美元的狗有很大的不同。狗和$是类型,你知道你可以用它们做什么。

You can program in assembly and make what you want, but you will observe how dangerous it is, because you can do almost what you want, especially weird things. Yes modifying an address value is dangerous, suppose you have an autonomous car that should deliver something at an address expressed in distance: 1200 memory street (address) and suppose in that street houses are separated by 100ft (1221 is a non valid address), if you are able to manipulate addresses as you like as integer, you would be able to try to deliver at 1223 and let the packet in the middle of the pavement.

你可以在汇编中编写程序并做出你想要的东西,但是你会发现它有多危险,因为你几乎可以做你想做的事情,尤其是奇怪的事情。是的修改一个地址值是危险的,假设你有一个自主车,它应该提供一个地址表示距离:内存街1200号(地址)和假设在那条街上的房子相距100英尺(1221是一个非有效地址),如果你能操纵地址一样你喜欢的整数,你可以试着提供1223,让包中间的人行道上。

Another example could be, house, address of the house, entry number in an address book of that address. All of these three are different concepts, different types...

另一个例子是,房子,房子的地址,那个地址的地址簿中的输入号。这三种都是不同的概念,不同的类型……

#9


9  

There are different types. And there is a good reason for it:

有不同的类型。这其中有一个很好的理由:

Having …

拥有……

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

… the expression …

的表情…

*b * 5

… is valid, while the expression …

…是有效的,而……

*c * 5

makes no sense.

毫无意义。

The big deal is not, how pointers or pointers-to-pointers are stored, but to what they refer.

重要的不是指针或指针对指针的存储方式,而是指针指向的内容。

#10


9  

The C language is strongly typed. This means that, for every address, there is a type, which tells the compiler how to interpret the value at that address.

C语言是强类型的。这意味着,对于每个地址,都有一个类型,它告诉编译器如何解释该地址的值。

In your example:

在你的例子:

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

The type of a is int, and the type of b is int * (read as "pointer to int"). Using your example, the memory would contain:

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.

类型并没有存储在内存中,只是编译器知道,当你读a的时候你会找到一个int,当你读b的时候你会找到一个你能找到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的类型是int **,读作“指向int的指针”。也就是说,对于编译器

  • c is a pointer;
  • c是一个指针;
  • when you read c, you get the address of another pointer;
  • 当你读c时,你会得到另一个指针的地址;
  • when you read that other pointer, you get the address of an int.
  • 当您读取另一个指针时,您将获得int的地址。

That is,

也就是说,

  • c is a pointer (int **);
  • c为指针(int **);
  • *c is also a pointer (int *);
  • *c也是一个指针(int *);
  • **c is an int.
  • * * c是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:

顺便说一下,这是一个普通的32位架构。对于大多数64位架构,您将拥有:

..... 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.

地址现在是8个字节,而int仍然只有4个字节。由于编译器知道每个变量的类型,所以它可以很容易地处理这种差异,并为指针读取8字节,为int读取4字节。

#11


6  

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 **.

操作符会产生一个指向对象的指针,这是一个类型为int *的类型,因此,赋值(通过初始化)将它赋值给b,也就是类型int *是有效的。&b产生一个指向对象b的指针,即&b是指向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.

C在赋值操作符(用于初始化)的约束中说(C11, 6.5.16.1p1):“两个操作数都是指向兼容类型的限定或非限定版本的指针”。但是在C定义中,兼容类型int **和int *是不兼容的类型。

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

在int *c = &b中有一个约束违反;初始化,这意味着编译器需要一个诊断。

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.

这里规则的一个基本原理是,标准不保证两个不同的指针类型是相同的大小(除了void *和字符指针类型),即sizeof (int *)和sizeof (int **)可以是不同的值。

#12


4  

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.

这是因为任何指针T*实际上都是指向T(或T的地址)的指针,而T是指向类型的指针。在这种情况下,*可以作为指向a(n)的指针读取,而T是指向类型的指针。

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.

这是用于解除引用的目的,其中dereference运算符将取一个T*,并返回一个类型为T的值。以及剩下的一切。

  *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.

注意,对于上述每个操作,dereference操作符的返回类型是如何与原始T*声明的T类型匹配的。

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.).

这极大地帮助了原始编译器和程序员解析指针类型:对于编译器,寻址操作符向类型添加一个*,去引用操作符从类型中删除一个*,任何不匹配都是错误的。对于程序员来说,*s的数量直接指示了您正在处理的中间方向的级别(int*总是指向int, float**总是指向float*,而float*总是指向float*,后者总是指向float,等等)。


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. 对于编译器来说,指针是非常困难的,因为它必须引用最近的赋值来确定间接的级别,并适当地确定返回类型。
  3. 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.
  4. 对于程序员来说,这个指针更难理解,因为很容易忘记中间有多少层。

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.

这个…这是一个非常危险的问题,通过让*s的数量直接表示间接的级别,可以很容易地解决这个问题。

#1


91  

In

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

You get a warning because &b is of type int **, and you try to initialize a variable of type int *. There's no implicit conversions between those two types, leading to the warning.

您会得到一个警告,因为b是类型int **,并且您尝试初始化类型为int *的变量。这两种类型之间不存在隐式转换,从而导致警告。

To take the longer example you want to work, if we try to dereference f the compiler will give us an int, not a pointer that we can further dereference.

举一个更长的例子,如果我们尝试去引用f编译器会给我们一个int,而不是一个我们可以去引用的指针。

Also note that on many systems int and int* are not the same size (e.g. a pointer may be 64 bits long and an int 32 bits long). If you dereference f and get an int, you lose half the value, and then you can't even cast it to a valid pointer.

还要注意,在许多系统中,int和int*的大小并不相同(例如,一个指针可能是64位长,一个int 32位长)。如果你去引用f并得到一个int,你失去了一半的值,然后你甚至不能把它转换成一个有效的指针。

#2


54  

If the purpose of pointer is just to save the memory address, I think there should be no hierarchy if the address we are going to save refers variable, pointer, double pointer, ... etc

如果指针的目的仅仅是保存内存地址,我认为如果我们要保存的地址是指变量,指针,双指针,…等

At runtime, yes, a pointer just holds an address. But at compile time there is also a type associated with every variable. As the others have said, int* and int** are two different, incompatible types.

在运行时,是的,指针只包含一个地址。但是在编译时,也有一个类型与每个变量相关联。正如其他人所说,int*和int**是两种不同的、不兼容的类型。

There is one type, void*, that does what you want: It stores only an address, you can assign any address to it:

有一种类型,void*,这就是你想要的:它只存储一个地址,你可以给它分配任何地址:

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

But when you want to dereference a void*, you need to supply the 'missing' type information yourself:

但是,当你想取消一个void*时,你需要自己提供“缺失”类型信息:

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

#3


23  

Now my question is, why does this type of code,

现在我的问题是,为什么这种类型的代码,

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

generate a warning?

产生一个警告?

You need to go back to the fundamentals.

你需要回到基本面。

  • variables have types
  • 变量的类型
  • variables hold values
  • 变量保存值
  • a pointer is a value
  • 指针是一个值
  • a pointer refers to a variable
  • 指针指的是一个变量
  • if p is a pointer value then *p is a variable
  • 如果p是一个指针值,那么*p是一个变量
  • if v is a variable then &v is a pointer
  • 如果v是变量,则&v是指针

And now we can find all the mistakes in your posting.

现在我们可以在你的帖子里找到所有的错误。

Then assume that now I want to save the address of pointer *b

然后假设现在我要保存指针*b的地址

No. *b is a variable of type int. It is not a pointer. b is a variable whose value is a pointer. *b is a variable whose value is an integer.

不。b是int类型的变量,不是指针。b是一个值为指针的变量。*b是一个值为整数的变量。

**c refers to the address of *b.

**c为*b地址。

NO NO NO. Absolutely not. You have to understand this correctly if you are going to understand pointers.

不不不绝对不是。如果你想理解指针,你必须正确地理解它。

*b is a variable; it is an alias for the variable a. The address of variable a is the value of variable b. **c does not refer to the address of a. Rather, it is a variable that is an alias for variable a. (And so is *b.)

* b是一个变量;它是变量a的别名。变量a的地址是变量b的值。**c没有引用a的地址。相反,它是变量a的别名(也就是*b)。

The correct statement is: the value of variable c is the address of b. Or, equivalently: the value of c is a pointer that refers to b.

正确的语句是:变量c的值是b的地址,或者等价地说,c的值是指向b的指针。

How do we know this? Go back to the fundamentals. You said that c = &b. So what is the value of c? A pointer. To what? b.

我们怎么知道的?回到基本面。你说的是c = &b。那么c的值是多少?一个指针。什么?b。

Make sure you fully understand the fundamental rules.

确保你完全理解基本规则。

Now that you hopefully understand the correct relationship between variables and pointers, you should be able to answer your question about why your code gives an error.

既然您希望理解变量和指针之间的正确关系,那么您应该能够回答关于为什么您的代码会出现错误的问题。

#4


20  

The type system of C requires this, if you want to get a correct warning and if you want the code to compile at all. With only one level of depth of pointers you wouldn't know if the pointer is pointing to a pointer or to an actual integer.

C的类型系统需要这样,如果您想获得正确的警告,如果您想编译代码的话。如果指针指向一个指针或一个实际的整数,你就不知道指针的深度。

If you dereference a type int** you know the type you get is int* and similarly if you dereference int* the type is int. With your proposal the type would be ambiguous.

如果你删除类型int**,你知道你得到的类型是int*,同样如果你删除类型int*,类型是int。

Taking from your example, it is impossible to know whether c points to a int or int*:

从你的例子中,不可能知道c是指向int还是int*:

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

What type is c pointing to? The compiler doesn't know that, so this next line is impossible to perform:

c指向什么类型?编译器不知道,所以下一行不可能执行:

*c;

In C all type information is lost after compiling, as every type is checked at compile-time and isn't needed anymore. Your proposal would actually waste memory and time as every pointer would have to have additional runtime information about the types contained in pointers.

在C语言中,所有类型信息在编译后都会丢失,因为每个类型都在编译时被检查,不再需要了。您的提议实际上会浪费内存和时间,因为每个指针都必须有关于指针中包含的类型的额外运行时信息。

#5


17  

Pointers are abstractions of memory addresses with additional type semantics, and in a language like C type matters.

指针是具有附加类型语义的内存地址的抽象,在像C类型这样的语言中很重要。

First of all, there's no guarantee that int * and int ** have the same size or representation (on modern desktop architectures they do, but you can't rely on it being universally true).

首先,不能保证int *和int **具有相同的大小或表示(在现代桌面体系结构中,它们具有相同的大小或表示,但是您不能指望它是通用的)。

Secondly, the type matters for pointer arithmetic. Given a pointer p of type T *, the expression p + 1 yields the address of the next object of type T. So, assume the following declarations:

其次,指针算法的类型很重要。给定一个T *类型的指针p,表达式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

The expression cp + 1 gives us the address of the next char object, which would be 0x1001. The expression sp + 1 gives us the address of the next short object, which would be 0x1002. ip + 1 gives us 0x1004, and lp + 1 gives us 0x1008.

表达式cp + 1给出了下一个char对象的地址,它将是0x1001。表达式sp + 1给出了下一个短对象的地址,即0x1002。ip + 1给出0x1004, lp + 1给出0x1008。

So, given

所以,鉴于

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

b + 1 gives us the address of the next int, and c + 1 gives us the address of the next pointer to int.

b + 1给出了下一个整数的地址,c + 1给出了下一个指针指向int的地址。

Pointer-to-pointers are required if you want a function to write to a parameter of pointer type. Take the following code:

如果希望将函数写入指针类型的参数,则需要指针对指针。下面的代码:

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
}

This is true for any type T. If we replace T with a pointer type P *, the code becomes

对于任何类型T都是如此,如果我们用指针类型P *替换T,那么代码就变成了

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
}

The semantics are exactly the same, it's just the types that are different; the formal parameter p is always one more level of indirection than the variable val.

语义完全相同,只是类型不同;形式参数p总是比变量val多一个层次的间接。

#6


11  

I think there should be no hierarchy if the address we are going to save refers variable, pointer, double pointer

我认为如果我们要保存的地址是指变量,指针,双指针,应该没有层次结构

Without the "hierarchy" it would be very easy to generate UB all over without any warnings - that would be horrible.

如果没有“层次结构”,就很容易在没有任何警告的情况下生成整个UB——这将是可怕的。

Consider this:

考虑一下:

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 ‘*’

The compiler gives me an error and thereby it helps me to know that I have done something wrong and I can correct the bug.

编译器给了我一个错误,因此它帮助我知道我做错了什么,我可以纠正错误。

But without "hierarchy", like:

但没有“等级制度”,如:

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

The compiler can't give any error as there are no "hierarchy".

编译器不能给出任何错误,因为没有“层次结构”。

But when the line:

但当线:

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

executes, it is UB (undefined behavior).

执行时,它是UB(未定义的行为)。

First *pc reads the char as if it was a pointer, i.e. probably reads 4 or 8 bytes even though we only reserved 1 byte. That is UB.

首先*pc像读取指针一样读取字符,即可能读取4或8字节,即使我们只保留1字节。乌兰巴托。

If the program didn't crash due to the UB above but just returned some garbish value, the second step would be to dereference the garbish value. Once again UB.

如果程序不是由于上面的UB而崩溃,而是返回了一些垃圾值,那么第二步就是取消垃圾值。再次乌兰巴托。

Conclusion

结论

The type system helps us to detect bugs by seeing int*, int**, int***, etc as different types.

类型系统通过将int*、int**、int**等视为不同的类型来帮助我们检测bug。

#7


10  

If the purpose of pointer is just to save the memory address, I think there should be no hierarchy if the address we are going to save refers variable, pointer, double pointer, ... etc. so below type of code should be valid.

如果指针的目的仅仅是保存内存地址,我认为如果我们要保存的地址是指变量,指针,双指针,…等等,所以下面的代码类型应该是有效的。

I think here is your misunderstanding: The purpose of the pointer itself is to store the memory address, but a pointer usually as well has a type so that we know what to expect at the place it points to.

我认为这是您的误解:指针本身的目的是存储内存地址,但是指针通常也有一个类型,这样我们就知道在它指向的地方会发生什么。

Especially, unlike you, other people really want to have this kind of hierarchy so as to know what to do with the memory contents which is pointed to by the pointer.

特别地,与你不同,其他人真的希望拥有这种层次结构,以便知道如何处理指针指向的内存内容。

It is the very point of C's pointer system to have type information attached to it.

将类型信息附加到C的指针系统中是最关键的。

If you do

如果你做

int a = 5;

&a implies that what you get is a int * so that if you dereference it is an int again.

与&意味着你得到的是一个int *,所以如果你取消引用,它又是一个int。

Bringing that to the next levels,

把它带到下一个层次,

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

&b is a pointer as well. But without knowing what hides behind it, resp. what it points to, it is useless. It is important to know that dereferencing a pointer reveals the type of the original type, so that *(&b) is an int *, and **(&b) is the original int value we work with.

b也是一个指针。但是不知道它的背后隐藏着什么,重新审视。它所指向的,是无用的。重要的是要知道去引用一个指针会显示原始类型的类型,所以*(&b)是一个int *,而**(&b)是我们使用的原始int值。

If you feel that in your circumstances there should be no hierarchy of types, you can always work with void *, although the direct usability is quite limited.

如果您认为在您的环境中不应该有类型的层次结构,您可以始终使用void *,尽管直接可用性非常有限。

#8


9  

If the purpose of pointer is just to save the memory address, I think there should be no hierarchy if the address we are going to save refers variable, pointer, double pointer, ... etc. so below type of code should be valid.

如果指针的目的仅仅是保存内存地址,我认为如果我们要保存的地址是指变量,指针,双指针,…等等,所以下面的代码类型应该是有效的。

Well that's true for the machine (after all roughly everything is a number). But in many languages variables are typed, means that the compiler can then ensure that you use them correctly (types impose a correct context on variables)

这对机器来说是正确的(毕竟几乎所有东西都是一个数字)。但是在许多语言中,变量是类型化的,这意味着编译器可以确保您正确地使用它们(类型将正确的上下文强加给变量)

It is true that a pointer to pointer and a pointer (probably) use the same amount of memory to store their value (beware this is not true for int and pointer to int, the size of an address is not related to the size of a house).

确实,指针和指针(很可能)使用相同数量的内存来存储它们的值(注意,这对于int和指针是不正确的,地址的大小与房子的大小没有关系)。

So if you have an address of an address you should use as is and not as a simple address because if you access the pointer to pointer as a simple pointer, then you would be able to manipulate an address of int as if it is a int, which is not (replace int without anything else and you should see the danger). You may be confused because all of this are numbers, but in everyday life you don't: I personally make a big difference in $1 and 1 dog. dog and $ are types, you know what you can do with them.

所以如果你有一个地址的地址你应该使用而不是一个简单的地址如果你访问指针的指针作为一个简单的指针,然后你能操纵一个地址的int,好像它是一个整数,这不是(取代int没有别的,你应该看到危险)。你可能会感到困惑,因为所有这些都是数字,但在日常生活中你不会:我个人对1美元和1美元的狗有很大的不同。狗和$是类型,你知道你可以用它们做什么。

You can program in assembly and make what you want, but you will observe how dangerous it is, because you can do almost what you want, especially weird things. Yes modifying an address value is dangerous, suppose you have an autonomous car that should deliver something at an address expressed in distance: 1200 memory street (address) and suppose in that street houses are separated by 100ft (1221 is a non valid address), if you are able to manipulate addresses as you like as integer, you would be able to try to deliver at 1223 and let the packet in the middle of the pavement.

你可以在汇编中编写程序并做出你想要的东西,但是你会发现它有多危险,因为你几乎可以做你想做的事情,尤其是奇怪的事情。是的修改一个地址值是危险的,假设你有一个自主车,它应该提供一个地址表示距离:内存街1200号(地址)和假设在那条街上的房子相距100英尺(1221是一个非有效地址),如果你能操纵地址一样你喜欢的整数,你可以试着提供1223,让包中间的人行道上。

Another example could be, house, address of the house, entry number in an address book of that address. All of these three are different concepts, different types...

另一个例子是,房子,房子的地址,那个地址的地址簿中的输入号。这三种都是不同的概念,不同的类型……

#9


9  

There are different types. And there is a good reason for it:

有不同的类型。这其中有一个很好的理由:

Having …

拥有……

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

… the expression …

的表情…

*b * 5

… is valid, while the expression …

…是有效的,而……

*c * 5

makes no sense.

毫无意义。

The big deal is not, how pointers or pointers-to-pointers are stored, but to what they refer.

重要的不是指针或指针对指针的存储方式,而是指针指向的内容。

#10


9  

The C language is strongly typed. This means that, for every address, there is a type, which tells the compiler how to interpret the value at that address.

C语言是强类型的。这意味着,对于每个地址,都有一个类型,它告诉编译器如何解释该地址的值。

In your example:

在你的例子:

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

The type of a is int, and the type of b is int * (read as "pointer to int"). Using your example, the memory would contain:

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.

类型并没有存储在内存中,只是编译器知道,当你读a的时候你会找到一个int,当你读b的时候你会找到一个你能找到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的类型是int **,读作“指向int的指针”。也就是说,对于编译器

  • c is a pointer;
  • c是一个指针;
  • when you read c, you get the address of another pointer;
  • 当你读c时,你会得到另一个指针的地址;
  • when you read that other pointer, you get the address of an int.
  • 当您读取另一个指针时,您将获得int的地址。

That is,

也就是说,

  • c is a pointer (int **);
  • c为指针(int **);
  • *c is also a pointer (int *);
  • *c也是一个指针(int *);
  • **c is an int.
  • * * c是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:

顺便说一下,这是一个普通的32位架构。对于大多数64位架构,您将拥有:

..... 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.

地址现在是8个字节,而int仍然只有4个字节。由于编译器知道每个变量的类型,所以它可以很容易地处理这种差异,并为指针读取8字节,为int读取4字节。

#11


6  

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 **.

操作符会产生一个指向对象的指针,这是一个类型为int *的类型,因此,赋值(通过初始化)将它赋值给b,也就是类型int *是有效的。&b产生一个指向对象b的指针,即&b是指向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.

C在赋值操作符(用于初始化)的约束中说(C11, 6.5.16.1p1):“两个操作数都是指向兼容类型的限定或非限定版本的指针”。但是在C定义中,兼容类型int **和int *是不兼容的类型。

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

在int *c = &b中有一个约束违反;初始化,这意味着编译器需要一个诊断。

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.

这里规则的一个基本原理是,标准不保证两个不同的指针类型是相同的大小(除了void *和字符指针类型),即sizeof (int *)和sizeof (int **)可以是不同的值。

#12


4  

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.

这是因为任何指针T*实际上都是指向T(或T的地址)的指针,而T是指向类型的指针。在这种情况下,*可以作为指向a(n)的指针读取,而T是指向类型的指针。

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.

这是用于解除引用的目的,其中dereference运算符将取一个T*,并返回一个类型为T的值。以及剩下的一切。

  *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.

注意,对于上述每个操作,dereference操作符的返回类型是如何与原始T*声明的T类型匹配的。

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.).

这极大地帮助了原始编译器和程序员解析指针类型:对于编译器,寻址操作符向类型添加一个*,去引用操作符从类型中删除一个*,任何不匹配都是错误的。对于程序员来说,*s的数量直接指示了您正在处理的中间方向的级别(int*总是指向int, float**总是指向float*,而float*总是指向float*,后者总是指向float,等等)。


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. 对于编译器来说,指针是非常困难的,因为它必须引用最近的赋值来确定间接的级别,并适当地确定返回类型。
  3. 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.
  4. 对于程序员来说,这个指针更难理解,因为很容易忘记中间有多少层。

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.

这个…这是一个非常危险的问题,通过让*s的数量直接表示间接的级别,可以很容易地解决这个问题。