d0r*_*ife 205 c pointers memory-address
在有关C的信誉良好的来源中,在讨论了&
运营商之后给出了以下信息:
...有点不幸的是,术语[地址]仍然存在,因为它混淆了那些不知道地址是什么的人,并误导那些做的人:把指针当作地址通常会导致悲伤...... .
我读过的其他材料(来自同等声誉的来源,我会说)总是毫不掩饰地将指针和&
操作符称为给出内存地址.我很想继续寻找事情的实际情况,但是当信誉良好的消息来源不同意时,这很难.
现在我有点困惑 - 究竟什么是指针,那么,如果不是内存地址?
PS
作者后来说:......我将继续使用"地址"一词,因为发明一个不同的 [术语] 会更糟.
Ale*_*nze 146
C标准没有定义指针在内部的内容以及它在内部的工作方式.这是故意的,以便不限制平台的数量,其中C可以实现为编译或解释语言.
指针值可以是某种ID或句柄,也可以是多个ID的组合(对x86段和偏移有问题),不一定是实际的内存地址.此ID可以是任何内容,甚至是固定大小的文本字符串.非地址表示对于C解释器可能特别有用.
Cor*_*lks 62
我不确定你的来源,但你所描述的语言类型来自C标准:
6.5.3.2地址和间接运算符
[...]
3. 一元&运算符产生其操作数的地址.[...]
所以...是的,指针指向内存地址.至少这就是C标准所暗示的意思.
更清楚地说,指针是一个保存某个地址值的变量.使用一元运算符返回对象的地址(可以存储在指针中).&
我可以将地址"42 Wallaby Way,Sydney"存储在变量中(并且该变量将是各种类型的"指针",但由于这不是内存地址,因此我们不能正确地称之为"指针").您的计算机具有其内存桶的地址.指针存储地址的值(即指针存储值"42 Wallaby Way,Sydney",这是一个地址).
编辑:我想扩展Alexey Frunze的评论.
什么是指针?我们来看看C标准:
6.2.5类型
[...]
20 [...]
甲指针类型可以由函数类型或对象类型派生,称为引用的类型.指针类型描述一个对象,其值提供对引用类型的实体的引用.从引用类型T派生的指针类型有时被称为"指向T"的指针.从引用类型构造指针类型称为"指针类型派生".指针类型是完整的对象类型.
本质上,指针存储一个值,该值提供对某个对象或函数的引用.的种类.指针旨在存储一个值,该值提供对某些对象或函数的引用,但情况并非总是如此:
6.3.2.3指针
[...]
5.整数可以转换为任何指针类型.除非先前指定,否则结果是实现定义的,可能未正确对齐,可能不指向引用类型的实体,并且可能是陷阱表示.
上面的引用说我们可以将整数转换为指针.如果我们这样做(也就是说,如果我们将整数值填充到指针而不是对对象或函数的特定引用),那么指针"可能不会指向引用类型的实体"(即它可能不提供引用对象或函数).它可能会为我们提供其他东西.这是一个你可能在指针中粘贴某种句柄或ID的地方(即指针不指向一个对象;它存储的是一个表示某个东西的值,但该值可能不是一个地址).
所以是的,正如Alexey Frunze所说,指针可能没有将地址存储到对象或函数中.有可能指针存储某种"句柄"或ID,你可以通过为指针分配一些任意整数值来实现.此句柄或ID表示的内容取决于系统/环境/上下文.只要您的系统/实现能够理解价值,您就会处于良好的状态(但这取决于具体的价值和具体的系统/实施).
通常,指针存储对象或函数的地址.如果它没有存储实际地址(对象或函数),则结果是实现定义的(意味着确切地发生了什么以及指针现在表示的内容取决于您的系统和实现,因此它可能是一个句柄或ID一个特定的系统,但在另一个系统上使用相同的代码/值可能会导致程序崩溃).
结果比我想象的还要长......
Har*_*nan 40
在这幅图片中,
pointer_p是一个位于0x12345的指针,指向0x34567处的变量variable_v.
Gil*_*il' 32
将指针视为地址是近似值.像所有近似值一样,它有时候很有用,但它也不准确,这意味着依赖它会导致麻烦.
指针就像一个地址,因为它指示了在哪里找到一个对象.这种类比的一个直接限制是并非所有指针实际上都包含一个地址.NULL
是一个不是地址的指针.指针变量的内容实际上可以是以下三种之一:
p
包含x
表达式的地址则表达式*p
具有相同的值x
);NULL
一个例子;p
没有有效值,则*p
可以执行任何操作("未定义的行为"),同时使程序崩溃相当普遍).此外,更准确地说,指针(如果有效且非空)包含地址:指针指示在何处查找对象,但有更多信息与其相关联.
特别是,指针具有类型.在大多数平台上,指针的类型在运行时没有影响,但它在编译时具有超出类型的影响.如果p
是指向int
(int *p;
)的指针,则p + 1
指向一个整数,该整数是sizeof(int)
之后的字节p
(假设p + 1
仍然是有效指针).如果q
指针指向与()char
相同的地址,则地址不同.如果您将指针视为地址,则对于指向同一位置的不同指针,"下一个地址"不同是不太直观的.p
char *q = p;
q + 1
p + 1
在某些环境中,有可能具有多个指针值,这些指针值具有指向存储器中相同位置的不同表示(存储器中的不同位模式).您可以将这些视为具有相同地址的不同指针,或同一位置的不同地址 - 在这种情况下隐喻并不清楚.该==
运营商总是告诉你两个操作数是否都指向同一位置,所以在这些环境中,你可以有p == q
,即使p
和q
有不同的位模式.
甚至存在指针在地址之外携带其他信息的环境,例如类型或许可信息.您可以轻松地完成作为程序员的生活,而不会遇到这些问题.
存在不同类型的指针具有不同表示的环境.您可以将其视为具有不同表示形式的不同类型的地址.例如,某些体系结构具有字节指针和字指针,或对象指针和函数指针.
总而言之,只要记住这一点,将指针视为地址并不算太糟糕
反过来更麻烦.并非所有看起来像地址的东西都可以成为指针.在任何指针深处的某处都表示为可以作为整数读取的位模式,你可以说这个整数是一个地址.但另一方面,并非每个整数都是指针.
首先有一些众所周知的局限; 例如,指定程序地址空间之外的位置的整数不能是有效指针.未对齐的地址不会为需要对齐的数据类型生成有效指针; 例如,在int
需要4字节对齐的平台上,0x7654321不能是有效值int*
.
但是,它远不止于此,因为当你将指针变成一个整数时,你就陷入了困境.这个问题的一个重要原因是优化编译器在微优化方面要比大多数程序员预期的好得多,因此他们对程序如何工作的心理模型是非常错误的.仅仅因为你有相同地址的指针并不意味着它们是等价的.例如,请考虑以下代码段:
unsigned int x = 0;
unsigned short *p = (unsigned short*)&x;
p[0] = 1;
printf("%u = %u\n", x, *p);
Run Code Online (Sandbox Code Playgroud)
您可能希望运行的设施,工厂的机器上,其中sizeof(int)==4
和sizeof(short)==2
,这无论是打印1 = 1?
(小端)或65536 = 1?
(大端).但是在我的带有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?
Run Code Online (Sandbox Code Playgroud)
GCC非常友好地警告我们在这个简单的例子中出了什么问题 - 在更复杂的例子中,编译器可能没有注意到.由于p
有不同的类型&x
,改变什么p
指向不能影响什么&x
指向(外部一些明确定义的例外).因此,编译器可以自由地保留x
寄存器中的值,而不是将此寄存器更新为*p
更改.该程序取消引用指向同一地址的两个指针,并获得两个不同的值!
这个例子的寓意是,只要你保持在C语言的精确规则内,将(非空有效)指针视为地址就可以了.硬币的另一面是C语言的规则是错综复杂的,除非你知道幕后发生的事情,否则难以获得直观的感觉.而在幕后发生的事情是,指针和地址之间的联系有点松散,既支持"异国情调"的处理器架构,又支持优化编译器.
因此,将指针视为您理解的第一步,但不要过多地遵循这种直觉.
Ani*_*nge 19
指针是HOLDS内存地址的变量,而不是地址本身.但是,您可以取消引用指针 - 并访问内存位置.
例如:
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*/
Run Code Online (Sandbox Code Playgroud)
而已.就这么简单.
一个演示我正在说什么的程序及其输出在这里:
该程序:
#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;
}
Run Code Online (Sandbox Code Playgroud)
tha*_*ang 16
很难准确地说出这些书籍的作者究竟是什么意思.指针是否包含地址取决于您如何定义地址以及如何定义指针.
从所有写入的答案来看,有些人认为(1)地址必须是整数,(2)指针不需要虚拟,因此在规范中没有这样说.有了这些假设,那么明确的指针不一定包含地址.
然而,我们看到虽然(2)可能是真的,(1)可能不一定是真的.根据@ CornStalks的回答,该怎么说&被称为运算符的地址?这是否意味着规范的作者想要一个包含地址的指针?
那么我们可以说,指针包含一个地址,但地址不一定是整数吗?也许.
我认为所有这些都是胡言乱语的迂腐语义.实际上,这完全没有价值.你能想到一个编译器以一种指针值不是地址的方式生成代码吗?如果是这样,什么?那正是我所想...
我认为本书的作者(声称指针不一定只是地址的第一个摘录)可能指的是指针带有固有的类型信息.
例如,
int x;
int* y = &x;
char* z = &x;
Run Code Online (Sandbox Code Playgroud)
y和z都是指针,但y + 1和z + 1是不同的.如果它们是内存地址,这些表达式不会给你相同的值吗?
而在这里,对指针的思考就好像它们是地址通常会导致悲伤.编写错误是因为人们认为指针好像是地址,这通常会导致悲伤.
55555可能不是指针,虽然它可能是一个地址,但是(int*)55555是一个指针.55555 + 1 = 55556,但是(int*)55555 + 1是55559(+/-以sizeof(int)表示的差异).
ale*_*xis 14
好吧,指针是表示内存位置的抽象.请注意,引用并没有说将指针视为内存地址是错误的,它只是说它"通常会导致悲伤".换句话说,它会导致你有不正确的期望.
最可能的悲痛来源当然是指针算术,这实际上是C的优势之一.如果指针是一个地址,你会期望指针算法是地址算术; 但事实并非如此.例如,向地址添加10应该为您提供一个大于10个寻址单元的地址; 但是向指针添加10会使其增加10倍于它所指向的对象的大小(甚至不是实际大小,而是向上舍入到对齐边界).在int *
具有32位整数的普通架构上,向其添加10将使其增加40个寻址单位(字节).经验丰富的C程序员已经意识到这一点并与之共存,但是你的作者显然不喜欢草率的隐喻.
还有一个问题是指针的内容如何表示内存位置:正如许多答案所解释的那样,地址并不总是int(或long).在某些体系结构中,地址是"段"加上偏移量.指针甚至可能只包含当前段("近"指针)的偏移量,它本身不是唯一的存储器地址.并且指针内容可能只与硬盘理解的内存地址有间接关系.但引用引用的作者甚至没有提到代表性,所以我认为这是概念上的等同性,而不是代表性.
Mar*_*sey 12
以下是我过去向一些困惑的人解释的方法:指针有两个影响其行为的属性.它有一个值,在(在典型环境中)是一个内存地址,一个类型,它告诉你它指向的对象的类型和大小.
例如,给定:
union {
int i;
char c;
} u;
Run Code Online (Sandbox Code Playgroud)
你可以有三个不同的指针都指向同一个对象:
void *v = &u;
int *i = &u.i;
char *c = &u.c;
Run Code Online (Sandbox Code Playgroud)
如果比较这些指针的值,它们都是相等的:
v==i && i==c
Run Code Online (Sandbox Code Playgroud)
但是,如果递增每个指针,您将看到它们指向的类型变得相关.
i++;
c++;
// You can't perform arithmetic on a void pointer, so no v++
i != c
Run Code Online (Sandbox Code Playgroud)
变量i
并且此时c
将具有不同的值,因为i++
导致i
包含下一个可访问整数的地址,并c++
导致c
指向下一个可寻址字符.通常,整数占用的内存多于字符,因此i
最终会得到比c
它们递增后更大的值.
Mark Bessey已经说过了,但在理解之前需要再次强调.
指针与变量有很大关系,而不是文字3.
指针是值(地址)和类型(具有附加属性,如只读)的元组.类型(以及附加参数,如果有的话)可以进一步定义或限制上下文; 例如.__far ptr, __near ptr
:地址的上下文是什么:堆栈,堆,线性地址,偏离某处,物理内存或什么.
它是类型的属性,使指针算术与整数算术略有不同.
不是变量的指针的计数器示例太多而不能忽略
堆栈指针或帧指针通常是不可寻址的寄存器
*(int *)0x1231330 = 13;
- 将任意整数值转换为pointer_of_integer类型并写入/读取整数而不引入变量
在C程序的生命周期中,将有许多其他没有地址的临时指针实例 - 因此它们不是变量,而是具有编译时关联类型的表达式/值.
与C中的任何其他变量一样,指针基本上是位的集合,其可以由一个或多个连接unsigned char
值表示(与任何其他类型的可用值一样,sizeof(some_variable)
将指示unsigned char
值的数量).使指针与其他变量不同的是,C编译器将把指针中的位解释为以某种方式标识可以存储变量的位置.在C中,与其他一些语言不同,可以为多个变量请求空间,然后将指针转换为该集合中的任何值,指向该集合中的任何其他变量.
许多编译器通过使用它们的位存储实际的机器地址来实现指针,但这不是唯一可能的实现.实现可以保留一个数组 - 用户代码无法访问 - 列出程序正在使用的所有内存对象(变量集)的硬件地址和分配大小,并且每个指针包含一个数组的索引与该索引的偏移量.这样的设计允许系统不仅将代码限制为仅对其拥有的内存进行操作,而且还确保指向一个内存项的指针不会被意外转换为指向另一个内存项的指针(在使用硬件的系统中)地址,if foo
和bar
是连续存储在内存中的10个项目的数组,指向"第十一"项的指针foo
可能改为指向第一项bar
,但是在系统中每个"指针"是一个对象ID和一个偏移量,如果代码试图将指针索引到foo
超出其分配范围,系统可能会陷阱.这样的系统也可以消除存储器碎片问题,因为与任何指针相关联的物理地址可以被移动.
请注意,虽然指针有点抽象,但它们不够抽象,不足以允许完全符合标准的C编译器实现垃圾收集器.C编译器指定每个变量(包括指针)都表示为unsigned char
值序列.给定任何变量,可以将其分解为数字序列,然后将该数字序列转换回原始类型的变量.因此,程序可以calloc
存储(接收指向它的指针),在那里存储东西,将指针分解为一系列字节,在屏幕上显示那些,然后擦除对它们的所有引用.如果程序然后从键盘接受了一些数字,将它们重新组合成指针,然后尝试从该指针读取数据,如果用户输入的程序早先显示的数字相同,则程序将需要输出数据已经存储在calloc
'ed memory中.由于没有可想到的方式,计算机可以知道用户是否已经制作了所显示的数字的副本,因此计算机不可能知道将来是否可能访问上述存储器.
指针是一种在C/C++中本机可用的变量类型,包含一个内存地址.与任何其他变量一样,它具有自己的地址并占用内存(数量是特定于平台的).
您将看到由于混淆而导致的一个问题是尝试通过简单地按值传递指针来更改函数内的指示对象.这将在函数范围内复制指针,并且对新指针"指向"的位置的任何更改都不会更改调用该函数的范围内指针的指示对象.为了修改函数内的实际指针,通常会将指针传递给指针.
摘要 (我也将在顶部):
(0)将指针视为地址通常是一种很好的学习工具,并且通常是指向普通数据类型的指针的实际实现.
(1)但是在许多(也许是大多数)编译器指向函数的指针不是地址,而是大于地址(通常是2x,有时更多),或者实际上是指向内存中的结构的指针,而不是包含函数的地址和类似的东西一个恒定的池.
(2)指向数据成员和指向方法的指针往往更加陌生.
(3)具有FAR和NEAR指针问题的旧版x86代码
(4)几个例子,最着名的是IBM AS/400,带有安全的"胖指针".
我相信你能找到更多.
详情:
UMMPPHHH !!!!! 到目前为止,许多答案都是相当典型的"程序员weenie"答案 - 但不是编译器weenie或硬件weenie.由于我假装是硬件weenie,并经常使用编译器weenies,让我投入我的两分钱:
在许多(可能是大多数)C编译器上,指向类型数据的指针T
实际上是地址T
.
精细.
但是,即使在许多这些编译器中,某些指针也不是地址.你可以通过观察来判断sizeof(ThePointer)
.
例如,指向函数的指针有时比普通地址大很多.或者,它们可能涉及一定程度的间接. 本文提供了一个涉及Intel Itanium处理器的描述,但我见过其他人.通常,要调用函数,您不仅必须知道函数代码的地址,还要知道函数的常量池的地址 - 一个内存区域,通过单个加载指令从中加载常量,而不是编译器必须生成几个Load Immediate和Shift和OR指令中的64位常量.因此,您需要2个64位地址,而不是单个64位地址.一些ABI(应用程序二进制接口)将其移动为128位,而其他ABI使用间接级别,其中函数指针实际上是包含刚刚提到的2个实际地址的函数描述符的地址.哪个更好?取决于您的观点:性能,代码大小和一些兼容性问题 - 通常代码假定指针可以转换为long或long long,但也可能假设long long正好是64位.此类代码可能不符合标准,但客户可能希望它能够运行.
我们中的许多人都对旧的Intel x86分段架构有着痛苦的回忆,其中包括NEAR POINTER和FAR POINTERS.值得庆幸的是,这些几乎已经灭绝了,所以只有一个简短的总结:在16位实模式中,实际的线性地址是
LinearAddress = SegmentRegister[SegNum].base << 4 + Offset
Run Code Online (Sandbox Code Playgroud)
而在保护模式下,它可能是
LinearAddress = SegmentRegister[SegNum].base + offset
Run Code Online (Sandbox Code Playgroud)
根据段中设置的限制检查生成的地址.有些程序实际上并没有使用标准的C/C++ FAR和NEAR指针声明,但很多人只是说*T
- 但是有编译器和链接器开关所以,例如,代码指针可能在指针附近,只有32位偏移对着CS(代码段)寄存器,而数据指针可能是FAR指针,指定一个16位段号和一个48位值的32位偏移量.现在,这两个数量肯定都与地址有关,但由于它们的大小不同,它们中的哪一个是地址?此外,除了与实际地址相关的内容之外,这些段还带有权限 - 只读,读写,可执行 - .
一个更有趣的例子,恕我直言,是(或许是)IBM AS/400系列.这台计算机是最早用C++实现操作系统的计算机之一.这个机器上的指针通常是实际地址大小的2倍 - 例如,正如这个演示文稿所说,128位指针,但实际地址是48-64位,而且,一些额外的信息,所谓的功能,提供了权限,如作为读,写,以及防止缓冲区溢出的限制.是的:你可以与C/C++兼容 - 如果这种情况无处不在,中国人民解放军和斯拉夫黑手党就不会侵入太多的西方计算机系统.但从历史上看,大多数C/C++编程都忽略了性能安全性.最有趣的是,AS400系列允许操作系统创建安全指针,这些指针可以提供给非特权代码,但是无特权代码无法伪造或篡改.同样,安全性,并且在符合标准的情况下,非常草率的非标准兼容的C/C++代码将无法在这样的安全系统中工作.同样,有官方标准,并有事实上的标准.
现在,我将离开我的安全肥皂盒,并提到一些指针(各种类型)通常不是真正地址的其他方式:指向数据成员的指针,指向成员函数方法的指针及其静态版本大于普通地址.正如这篇文章 所说:
有许多方法可以解决这个问题[与单一与多重传播和虚拟继承相关的问题].以下是Visual Studio编译器决定处理它的方法:指向乘法继承类的成员函数的指针实际上是一个结构."然后他们继续说"转换函数指针可以改变它的大小!".
正如您可能从我对安全性的指导中猜测的那样,我参与了C/C++硬件/软件项目,其中指针被视为功能而不是原始地址.
我可以继续,但我希望你能得到这个想法.
摘要 (我也将在顶部):
(0)将指针视为地址通常是一种很好的学习工具,并且通常是指向普通数据类型的指针的实际实现.
(1)但是在许多(也许是大多数)编译器指向函数的指针不是地址,而是比地址大(通常是2X,有时更多),或实际上是指向内存中的结构的指针而不是包含函数的地址和类似的东西一个恒定的池.
(2)指向数据成员和指向方法的指针往往更加陌生.
(3)具有FAR和NEAR指针问题的旧版x86代码
(4)几个例子,最着名的是IBM AS/400,带有安全的"胖指针".
我相信你能找到更多.
归档时间: |
|
查看次数: |
31555 次 |
最近记录: |