指针指向正常指针
指针的目的是保存特定variables的地址。 那么下面代码的内存结构应该是这样的:
int a = 5; int *b = &a;
……内存地址……值
一个… 0x000002 ………………. 5
b … 0x000010 ………………. 0x000002
好的。 那么现在假设我想保存指针* b的地址。 那么我们通常定义一个双指针** c,as
int a = 5; int *b = &a; int **c = &b;
然后内存结构如下所示:
……内存地址……值
一个… 0x000002 ………………. 5
b … 0x000010 ………………. 0x000002
c … 0x000020 ………………. 0x000010
所以** c引用* b的地址。
现在我的问题是,为什么这种types的代码,
int a = 5; int *b = &a; int *c = &b;
生成警告?
如果指针的目的只是为了保存内存地址,我认为如果我们要保存的地址指的是一个variables,一个指针,一个双指针等,那么应该没有层次结构,所以下面的代码types应该有效。
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
的types是int **
,并且你尝试初始化一个int *
types的variables。 这两种types之间没有隐含的转换,导致警告。
如果我们试图解引用f
,编译器会给我们一个int
,而不是一个可以进一步解引用的指针。
还要注意在很多系统中int
和int*
的大小不一样(例如一个指针可能长64位, int
长32位)。 如果你解引用f
并得到一个int
,那么你将失去一半的值,然后你甚至不能把它转换成一个有效的指针。
如果指针的目的只是为了保存内存地址,我认为如果我们要保存的地址是指variables,指针,双指针等等,则不应该有层次结构。
在运行时,是的,指针只是一个地址。 但在编译时还有一个与每个variables相关的types。 正如其他人所说, int*
和int**
是两个不同的,不兼容的types。
有一种types, void*
,你想要什么:它只存储一个地址,你可以分配任何地址给它:
int a = 5; int *b = &a; void *c = &b;
但是当你想解除一个void*
,你需要自己提供'missing'types的信息:
int a2 = **((int**)c);
现在我的问题是,为什么这种types的代码,
int a = 5; int *b = &a; int *c = &b;
生成警告?
你需要回到基本面。
- variables有types
- variables保存值
- 一个指针是一个值
- 一个指针指向一个variables
- 如果
p
是一个指针值,那么*p
是一个variables - 如果
v
是一个variables,那么&v
是一个指针
现在我们可以find你发布的所有错误。
那么现在假设我想保存指针
*b
的地址
不。 *b
是inttypes的variables。 这不是一个指针。 b
是一个variables,其值是一个指针。 *b
是一个整数值的variables 。
**c
是指*b
的地址。
不不不。 绝对不。 如果你要了解指针,你必须正确理解这个。
*b
是一个variables; 它是variablesa
的别名。 variablesa
的地址是variablesb
的值。 **c
不是指地址。 相反,它是一个variables ,是variablesa
的别名 。 (还有*b
)
正确的说法是:variablesc
的值是b
的地址 。 或者等价地: c
的值是一个指向b
的指针。
我们怎么知道呢? 回到基本面。 你说c = &b
。 那么c
的价值是什么? 一个指针。 什么? b
。
确保你完全理解基本规则。
现在,你希望理解variables和指针之间的正确关系,你应该能够回答你的问题,为什么你的代码给出了一个错误。
C的types系统需要这个,如果你想得到一个正确的警告,如果你想要的代码编译。 只有一层指针深度你不知道指针是指向一个指针还是指向一个实际的整数。
如果你取消引用typesint**
你知道你得到的types是int*
,类似的,如果你取消引用int*
types是int
。 与您的build议types将是模糊的。
从你的例子中,不可能知道c
指向一个int
还是int*
:
c = rand() % 2 == 0 ? &a : &b;
c指的是什么types? 编译器不知道,所以这下一行是不可能执行的:
*c;
在C中,所有types信息在编译后都会丢失,因为每个types在编译时都被检查过,而且不再需要。 您的提议实际上会浪费内存和时间,因为每个指针都必须具有有关指针中包含的types的附加运行时信息。
指针是带有附加types语义的内存地址的抽象 ,并且像Ctypes的语言一样。
首先,不能保证int *
和int **
具有相同的大小或表示forms(在现代桌面体系结构上它们所做的事情,但是不能依赖普遍真实性)。
其次,指针运算的types很重要。 给定一个T *
types的指针p
,expression式p + 1
产生下一个types为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
expression式cp + 1
给了我们下一个char
对象的地址,它将是0x1001
。 expression式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
指针的地址。
如果您想要函数写入指针types的参数,则指针指针是必需的。 采取以下代码:
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 }
对于任何typesT
都是如此。 如果用指针typesP *
replaceT
,代码就变成了
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 }
语义完全一样,只是不同的types; forms参数p
总是比variablesval
多一个间接级别。
我认为如果我们要保存的地址是指variables,指针,双指针,那么不应该有层次结构
如果没有“等级制”,那么就很容易在没有任何警告的情况下生成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
,就好像它是一个指针,即可能读取4或8个字节,即使我们只保留1个字节。 那是UB。
如果程序没有因上面的UB而崩溃,只是返回了一些垃圾的价值,那么第二步就是去掉垃圾的价值。 再次UB。
结论
types系统通过将int *,int **,int ***等视为不同types来帮助我们检测错误。
如果指针的目的只是为了保存内存地址,我认为如果我们要保存的地址是指variables,指针,双指针等,那么应该没有层次结构,所以下面的代码types应该是有效的。
我认为这里是你的误解:指针本身的目的是存储内存地址,但是一个指针通常也有一个types,以便我们知道它指向的地方会发生什么。
特别的是,与你不同的是,其他人真的希望有这样的层次结构,以便知道如何处理指针指向的内存内容。
C的指针系统的重点在于它附带了types信息。
如果你这样做
int a = 5;
&a
意味着你得到的是一个int *
所以如果你解引用它是一个int
再次。
把它提高到一个新的水平,
int *b = &a; int **c = &b;
&b
也是一个指针。 但是不知道背后隐藏着什么, 它指的是什么,它是无用的。 知道取消引用指针显示原始types的types是非常重要的,所以*(&b)
是一个int *
,而**(&b)
是我们使用的原始int
值。
如果你觉得在你的情况下不应该有types的层次结构,你总是可以使用void *
,尽pipe直接的可用性是相当有限的。
如果指针的目的只是为了保存内存地址,我认为如果我们要保存的地址是指variables,指针,双指针等,那么应该没有层次结构,所以下面的代码types应该是有效的。
那么这是真的机器(毕竟大致一切都是一个数字)。 但是在许多语言中,variables是键入的,意味着编译器可以确保正确使用它们(types在variables上施加正确的上下文)
确实,指向指针和指针(可能)的指针使用相同数量的内存来存储它们的值(注意,对于int和指向int的指针,这不是真的,地址的大小与大小无关屋)。
所以,如果你有一个地址的地址,你应该使用现在,而不是一个简单的地址,因为如果你访问指针指针作为一个简单的指针,那么你将能够操纵一个int的地址,就好像它是一个int ,这是不(取代int没有别的,你应该看到危险)。 你可能会感到困惑,因为所有这些都是数字,但是在日常生活中你不会:我个人对1美元和1美元的狗有很大的影响。 狗和$是types,你知道你可以用它们做什么。
你可以组装程序,制作你想要的东西,但是你会发现它有多危险,因为你几乎可以做你想要的东西,特别是奇怪的东西。 是修改一个地址值是危险的,假设你有一辆自动驾驶的汽车,应该以距离表示的地址递送东西:1200内存街道(地址),假设街道房屋相隔100英尺(1221是非有效地址),如果您可以像整数一样操作地址,则可以尝试在1223处递送,并将包放在人行道中间。
另一个例子可以是房子,房子的地址,在该地址的地址簿中的条目号。 所有这三个是不同的概念,不同的types…
有不同的types。 有一个很好的理由:
有…
int a = 5; int *b = &a; int **c = &b;
… expression方式 …
*b * 5
…是有效的,而expression式…
*c * 5
没有意义。
重要的不是,指针或指针指针是如何存储的,而是指向的是什么 。
C语言是强types的。 这意味着,对于每个地址,都有一个types ,它告诉编译器如何解释该地址的值。
在你的例子中:
int a = 5; int *b = &a;
a的types是int
, b
的types是int *
(读为“指向int
指针”)。 使用你的例子,内存将包含:
..... memory address ...... value ........ type a ... 0x00000002 .......... 5 ............ int b ... 0x00000010 .......... 0x00000002 ... int*
types实际上并不存储在内存中,只是编译器知道,当你读到a
int
,你会发现一个int
,当你读到b
你会发现一个地方的地址,你可以find一个int
。
在你的第二个例子中:
int a = 5; int *b = &a; int **c = &b;
c
的types是int **
,读为“指向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**
由于“types”不与值一起存储,并且指针可以指向任何内存地址,所以编译器知道地址处的值的types基本上是通过指针的types,并删除最右边的*
。
顺便说一下,这是一个常见的32位体系结构。 对于大多数64位体系结构,您将拥有:
..... memory address .............. value ................ type a ... 0x0000000000000002 .......... 5 .................... int b ... 0x0000000000000010 .......... 0x0000000000000002 ... int* c ... 0x0000000000000020 .......... 0x0000000000000010 ... int**
地址现在每个8个字节,而一个int
仍然只有4个字节。 由于编译器知道每个variables的types ,所以它可以很容易地处理这个差异,并读取一个指针的8个字节和int
4个字节。
为什么这种types的代码会产生警告?
int a = 5; int *b = &a; int *c = &b;
&
运算符产生一个指向该对象的指针,即&a
是int *
型的types,因此将它赋值给b
也是int *
types的。 &b
产生一个指向对象b
的指针,即&b
是指向int *
,i。, int **
types的指针。
C在赋值运算符的约束(在初始化过程中)中说:(C11,6.5.16.1p1): “两个操作数都是指向兼容types的合格或不合格版本的指针” 。 但是在C中定义什么是兼容typesint **
和int *
是不兼容的types。
所以在int *c = &b;
初始化意味着编译器需要诊断。
这里规则的一个基本原理是标准没有保证两个不同的指针types是相同的大小( void *
和字符指针types除外),即sizeof (int *)
和sizeof (int **)
可以是不同的值。
那是因为任何指针T*
实际上是pointer to a T
(或pointer to a T
address of a T
)的typespointer to a T
,其中T
是指向的types。 在这种情况下, *
可以被读为pointer to a(n)
,而T
是指向的types。
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*
这用于解引用的目的,其中解引用运算符将采用T*
,并返回types为T
的值。 返回types可以被看作是截断最左边的“指向(n)”的指针,并且是剩余的。
*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".
请注意,对于上述每个操作,解引用运算符的返回types与原始T*
声明的T
types相匹配。
这极大地帮助原始编译器和程序员parsing指针的types:对于编译器,地址 – 运算符向types添加*
,解引用运算符从types中移除*
,任何不匹配都是错误。 对于一个程序员来说, *
s的数量直接表明了你处理的间接级别( int*
总是指向int
, float**
总是指向float*
,这总是指向float
)。 )。
现在,考虑到这一点,不pipe间接级别的数目如何,仅使用一个单一的问题有两个主要问题:
- 指针编译器解引用要困难得多,因为它必须引用回最近的赋值来确定间接级别,并适当地确定返回types。
- 指针对于程序员来说更难以理解,因为很容易忘记有多less层间接存在。
在这两种情况下,确定值的实际types的唯一方法是回溯它,迫使你去别的地方找它。
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(). }
这是一个非常危险的问题,通过拥有多个*
s很容易解决的问题直接代表了间接性的层次。