指针与普通指针的指针

use*_*298 74 c pointers

指针的目的是保存特定变量的地址.那么下面代码的内存结构应如下所示:

int a = 5;
int *b = &a;
Run Code Online (Sandbox Code Playgroud)

......内存地址......值 a ... 0x000002
................... 5
b ... 0x000010 ..... .............. 0x000002

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

int a = 5;
int *b = &a;
int **c = &b;
Run Code Online (Sandbox Code Playgroud)

然后内存结构看起来像:

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

所以**c指的是*b的地址.

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

int a = 5;
int *b = &a;
int *c = &b;
Run Code Online (Sandbox Code Playgroud)

产生警告?

如果指针的目的只是为了保存内存地址,我认为如果我们要保存的地址是指变量,指针,双指针等,那么应该没有层次结构,所以下面的代码类型应该是有效.

int a = 5;
int *b = &a;
int *c = &b;
int *d = &c;
int *e = &d;
int *f = &e;
Run Code Online (Sandbox Code Playgroud)

Som*_*ude 90

int a = 5;
int *b = &a;   
int *c = &b;
Run Code Online (Sandbox Code Playgroud)

您收到警告,因为&b它是类型int **,并且您尝试初始化类型的变量int *.这两种类型之间没有隐式转换,导致警告.

要采用你想要工作的更长的例子,如果我们尝试取消引用f,编译器会给我们一个int,而不是一个我们可以进一步解引用的指针.

还要注意,在许多系统上int并且int*大小不同(例如,指针可能是64位长,int32位长).如果取消引用f并获得一个int,则会丢失一半的值,然后您甚至无法将其转换为有效指针.

  • 在C设计的系统(以及一些现代嵌入式系统)上,同一程序中有不同类型的指针 - 例如,近指针和远指针,或数据和代码指针.*指针不是int值,人; 假装它只是因为它"主要起作用"*. (4认同)
  • @Luaan:C是为早期的DEC小型机设计的,从20世纪70年代开始在PDP-11上最常见.那些机器没有"近"和"远"指针."近"和"远"是微软对C的扩展,以支持IBM PC上的脑死机英特尔8086/8088分段架构.(在PC项目开始的时候,IBM正在另一个站点生产和销售基于摩托罗拉68000的实验室计算机.想象一下,如果这两个站点已经谈过话,那么单独使用阿司匹林就可以节省成本,避免分段头痛......) (2认同)

ala*_*ain 53

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

在运行时,是的,指针只保存一个地址.但是在编译时,还有一个与每个变量相关联的类型.正如其他人所说,int*并且int**是两种不同的,不兼容的类型.

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

int a = 5;
int *b = &a;
void *c = &b;
Run Code Online (Sandbox Code Playgroud)

但是当你想要取消引用时void*,你需要自己提供"缺失"的类型信息:

int a2 = **((int**)c);
Run Code Online (Sandbox Code Playgroud)


Eri*_*ert 23

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

int a = 5; 
int *b = &a; 
int *c = &b; 
Run Code Online (Sandbox Code Playgroud)

产生警告?

你需要回到基础.

  • 变量有类型
  • 变量保持值
  • 指针是一个值
  • 指针指的是一个变量
  • if p是指针值,那么它*p是一个变量
  • 如果v是变量则&v是指针

现在我们可以找到你发布的所有错误.

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

No. *b是int类型的变量.它不是指针.b是一个变量,其是一个指针. *b是一个变量,其值是一个整数.

**c是指的地址*b.

不不不.绝对不.如果你要理解指针,你必须正确理解这一点.

*b是一个变量; 它是变量的别名a.变量的地址是变量a的值b. **c没有提到的地址a.相反,它是一个变量,其为别名为变量a.(等等*b.)

正确的说法是:变量c地址b.或者,等效地:值c是指向的指针b.

我们怎么知道呢?回到基础.你这么说c = &b.那么价值是c多少?一个指针.要什么?b.

确保您完全理解基本规则.

现在您希望了解变量和指针之间的正确关系,您应该能够回答有关代码为什么会出错的问题.

  • @mbrig:这是Dennis Ritchie的天才主意.如果不清楚*为什么*这是一个天才的想法,你就不会在心理上正确地解析语言.当我们说'int*b;`时,我们不仅仅是*说'`b`是`int*`类型的变量.**我们也说`*b`是`int`类型的变量*.这里的天才想法是你可以在心理上将它视为`int*b`和`int*b`,并且两种解释都是正确的. (6认同)

250*_*501 20

如果你想获得正确的警告并且你想要编译代码,那么C的类型系统需要这个.只有一个指针深度,你不知道指针是指向指针还是指向实际整数.

如果你取消引用一种类型,int**你知道你得到的类型int*,如果你取消引用,类似int*的是int.根据您的提案,类型将是模棱两可的.

从你的例子中,不可能知道是否c指向a intint*:

c = rand() % 2 == 0 ? &a : &b;
Run Code Online (Sandbox Code Playgroud)

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

*c;
Run Code Online (Sandbox Code Playgroud)

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


Joh*_*ode 17

指针是具有附加类型语义的内存地址的抽象,并且在诸如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
Run Code Online (Sandbox Code Playgroud)

表达式cp + 1为我们提供了下一个char对象的地址0x1001.表达式sp + 1为我们提供了下一个short对象的地址0x1002. ip + 1给了我们0x1004,并lp + 1给了我们0x1008.

所以,给定

int a = 5;
int *b = &a;
int **c = &b;
Run Code Online (Sandbox Code Playgroud)

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
}
Run Code Online (Sandbox Code Playgroud)

这适用于任何类型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
}
Run Code Online (Sandbox Code Playgroud)

语义完全相同,它只是不同的类型; 形式参数p总是比变量更多的间接级别val.


438*_*427 11

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

没有"层次结构",在没有任何警告的情况下生成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 ‘*’
Run Code Online (Sandbox Code Playgroud)

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

但没有"等级",如:

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
Run Code Online (Sandbox Code Playgroud)

由于没有"层次结构",编译器不能给出任何错误.

但当行:

printf("%c\n", **pc);
Run Code Online (Sandbox Code Playgroud)

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

首先*pc读取char它就好像它是一个指针,即使我们只保留1个字节,也可能读取4或8个字节.那是UB.

如果程序由于上面的UB没有崩溃但只返回了一些垃圾值,那么第二步就是取消引用垃圾值.再一次UB.

结论

类型系统通过将int*,int**,int***等视为不同类型来帮助我们检测错误.


glg*_*lgl 10

如果指针的目的只是为了保存内存地址,我认为如果我们要保存的地址是变量,指针,双指针等等,那么应该没有层次结构,所以下面的代码类型应该是有效的.

我认为这是你的误解:指针本身的目的是存储内存地址,但指针通常也有一个类型,以便我们知道在它指向的位置会发生什么.

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

C指针系统的特点是附加了类型信息.

如果你这样做

int a = 5;
Run Code Online (Sandbox Code Playgroud)

&a意味着你得到的是一个int *如果你取消引用它是一个int又一个.

把它带到下一个层次,

int *b = &a;
int **c = &b;
Run Code Online (Sandbox Code Playgroud)

&b也是一个指针.但是不知道背后隐藏着什么,分别是.它指向什么,它是无用的.重要的是要知道解除引用指针会显示原始类型的类型,因此它*(&b)是一个int *,并且**(&b)int我们使用的原始值.

如果您认为在您的情况下应该没有类型的层次结构,您可以随时使用void *,尽管直接可用性非常有限.


Jea*_*nès 9

如果指针的目的只是为了保存内存地址,我认为如果我们要保存的地址是变量,指针,双指针等等,那么应该没有层次结构,所以下面的代码类型应该是有效的.

那对于机器来说是真的(毕竟大概都是一个数字).但是在许多语言中键入变量,意味着编译器可以确保您正确使用它们(类型对变量强加正确的上下文)

确实,指向指针和指针(可能)的指针使用相同数量的内存来存储它们的值(请注意,对于int和指向int的指针不是这样,地址的大小与a的大小无关)屋).

因此,如果您有一个地址的地址,您应该按原样使用而不是简单的地址,因为如果您将指针作为一个简单的指针访问指针,那么您将能够操作int的地址,就好像它是一个int ,这不是(没有任何其他东西替换int,你应该看到危险).你可能会感到困惑,因为所有这些都是数字,但在日常生活中你不会:我个人在1美元和1只狗身上有很大的不同.狗和$是类型,你知道你可以用它们做什么.

你可以在装配中编程并制作你想要的东西,但你会发现它有多危险,因为你几乎可以做你想做的事情,特别是奇怪的事情.是的,修改一个地址值是危险的,假设你有一辆自动驾驶汽车应该以距离表示的地址提供东西:1200内存街道(地址),并假设街道房屋相隔100英尺(1221是无效地址),如果你能够像整数那样操纵地址,你就可以尝试在1223处交付并让数据包在人行道中间.

另一个例子可以是房屋,房屋的地址,该地址的地址簿中的入口号.所有这三个都是不同的概念,不同的类型......


Ami*_*wad 9

有不同的类型.并且有充分的理由:

有......

int a = 5;
int *b = &a;
int **c = &b;
Run Code Online (Sandbox Code Playgroud)

… 表达方式 …

*b * 5
Run Code Online (Sandbox Code Playgroud)

......是有效的,而表达......

*c * 5
Run Code Online (Sandbox Code Playgroud)

没有意义.

大不了就是没有,怎么指针或指针到指针存储,但什么他们参考.


Ces*_*arB 9

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

在你的例子中:

int a = 5;
int *b = &a;
Run Code Online (Sandbox Code Playgroud)

的类型的aIS int,和类型bint *(读作"指针int").使用您的示例,内存将包含:

..... memory address ...... value ........ type
a ... 0x00000002 .......... 5 ............ int
b ... 0x00000010 .......... 0x00000002 ... int*
Run Code Online (Sandbox Code Playgroud)

类型实际上并不存储在内存中,只是编译器知道,当您读到时,a您会找到一个int,当您阅读时,b您将找到一个可以找到的地方的地址int.

在你的第二个例子中:

int a = 5;
int *b = &a;
int **c = &b;
Run Code Online (Sandbox Code Playgroud)

类型cint **,读作"指向指针的指针int".这意味着,对于编译器:

  • c 是一个指针;
  • 当你读c,你得到另一个指针的地址;
  • 当你读到其他指针时,你会得到一个地址int.

那是,

  • c是一个指针(int **);
  • *c也是一个指针(int *);
  • **c是一个int.

内存将包含:

..... memory address ...... value ........ type
a ... 0x00000002 .......... 5 ............ int
b ... 0x00000010 .......... 0x00000002 ... int*
c ... 0x00000020 .......... 0x00000010 ... int**
Run Code Online (Sandbox Code Playgroud)

由于"类型"不与值一起存储,并且指针可以指向任何内存地址,因此编译器知道地址值的类型的方式基本上是通过获取指针的类型,并移除最右边的*.


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

..... memory address .............. value ................ type
a ... 0x0000000000000002 .......... 5 .................... int
b ... 0x0000000000000010 .......... 0x0000000000000002 ... int*
c ... 0x0000000000000020 .......... 0x0000000000000010 ... int**
Run Code Online (Sandbox Code Playgroud)

地址现在每个8个字节,而a int仍然只有4个字节.由于编译器知道每个变量的类型,因此它可以轻松处理这种差异,并为指针读取8个字节,为4个字节读取int.


oua*_*uah 6

为什么这种类型的代码会产生警告?

int a = 5;
int *b = &a;   
int *c = &b;
Run Code Online (Sandbox Code Playgroud)

&操作者产生一个指向对象,即&a是类型int *(通过初始化),以便在分配给b这也是类型int *是有效的.&b产生一个指向对象的指针b,它是&b指向的类型的指针int *,即int **.

C在赋值运算符(用于初始化)的约束中表示(C11,6.5.16.1p1):"两个操作数都是指向兼容类型的限定或非限定版本的指针".但是在C定义什么是兼容类型int **并且int *兼容类型.

因此int *c = &b;初始化中存在约束违规,这意味着编译器需要进行诊断.

一个规则的理由这里是没有保障的标准,这两个不同的指针类型的大小相同(除了void *和字符指针类型),也就是sizeof (int *)sizeof (int **)可以是不同的值.