我知道指针保存地址。我知道,指针的类型基于它们指向的数据的“类型”而被“一般地”知道。但是,指针仍然是变量,它们所持有的地址必须具有数据“类型”。根据我的信息,地址为十六进制格式。但是,我仍然不知道该十六进制是什么“数据”类型。(请注意,我知道十六进制是什么,但是10CBA20
例如,当您说这是一个字符串吗?整数吗?是什么?当我想访问该地址并对其进行操作..本身时,我需要知道其类型。这这就是为什么我问。)
我知道指针保存地址。我知道,指针的类型基于它们指向的数据的“类型”而被“一般地”知道。但是,指针仍然是变量,它们所持有的地址必须具有数据“类型”。根据我的信息,地址为十六进制格式。但是,我仍然不知道该十六进制是什么“数据”类型。(请注意,我知道十六进制是什么,但是10CBA20
例如,当您说这是一个字符串吗?整数吗?是什么?当我想访问该地址并对其进行操作..本身时,我需要知道其类型。这这就是为什么我问。)
Answers:
指针变量的类型是..指针。
在C语言中,您被正式允许执行的操作是将其与其他指针(或特殊的NULL /零值)进行比较,添加或减去整数或将其强制转换为其他指针。
接受未定义的行为后,您可以查看实际的值。它通常是一个机器字,与整数一样,并且通常可以无损地与整数类型进行转换。(很多Windows代码通过在DWORD或HANDLE typedef中隐藏指针来完成此操作)。
在某些体系结构中,指针不简单,因为内存不平坦。DOS / 8086``近''和``远''; PIC的不同内存和代码空间。
p1-p2
。结果是带符号的整数值。特别是&(array[i])-&(array[j]) == i-j
intptr_t
并uintptr_t
保证对于指针值“足够大”。
p
如果在c中实现依赖行为,则在printf中添加说明符将使人可读的空指针表示形式定义为a。
您太复杂了。
地址只是整数,句点。理想情况下,它们是引用的存储单元的数量(实际上,由于段,虚拟内存等原因,这会变得更加复杂)。
十六进制语法是一种完整的小说,仅为了方便程序员而存在。0x1A和26是完全相同的数字,并且类型完全相同,计算机也不使用-内部,计算机始终使用00011010(一系列二进制信号)。
编译器是否允许您将指针视为数字取决于语言定义-传统上,“系统编程”语言对事物的工作方式更加透明,而“高级”语言则更经常尝试隐藏裸机从程序员那里得到的-但这并没有改变指针是数字的事实,指针通常是数字的最常见类型(一种与您的处理器体系结构位数一样多的数字)。
指针就是-指针。没有别的了。不要试图认为这是另外一回事。
在C,C ++和Objective-C等语言中,数据指针具有四种可能的值:
还有一些函数指针,它们标识一个函数,或者为空函数指针,或者具有不确定的值。
其他指针是C ++中的“成员指针”。这些绝对不是内存地址!相反,它们标识类的任何实例的成员。在Objective-C中,您具有选择器,这些选择器类似于“指向具有给定方法名称和参数名称的实例方法的指针”。像成员指针一样,它标识所有类的所有方法,只要它们看起来相同即可。
您可以研究特定的编译器如何实现指针,但这是一个完全不同的问题。
class A { public: int num; int x; }; int A::*pmi = &A::num; A a; int n = a.*pmi;
如果变量pmi
不包含内存地址(即,如代码的最后一行所确定的那样,则为class num
的实例成员的地址),则该变量将没有太大用处。您可以将其强制转换为普通指针(尽管编译器可能会警告您)并成功取消引用(证明它是任何其他指针的语法糖)。a
A
int
指针是一种位模式,用于寻址(用于读取或写入目的的唯一标识)RAM中的存储字。出于历史和传统原因,更新单位为八位,英文为“字节”,法文为“八位位组”。这是普遍存在的,但不是固有的;其他尺寸已经存在。
如果我没记错的话,那是一台使用29位字的计算机。这不仅不是二的幂,甚至是素数。我以为这是SILLIAC,但相关的Wikipedia文章不支持这一点。CAN BUS使用29位地址,但是按照惯例,即使它们在功能上相同,也不会将其称为指针。
人们一直断言指针是整数。这既不是内在的也不是必需的,但是如果我们将位模式解释为整数,则通常会出现有用的质量,从而可以非常直接(因此在小型硬件上非常有效)实现诸如“字符串”和“数组”之类的结构。连续内存的概念取决于顺序的邻接关系,并且可以进行相对定位。整数比较和算术运算可以有意义地应用。因此,用于存储寻址的字长与ALU(进行整数数学运算的东西)之间几乎始终具有很强的相关性。
有时两者并不对应。在早期的PC中,地址总线为24位宽。
基本上,每台现代计算机都是一台按位按钮的计算机。通常,它会将数据簇中的位(字节,字,dwords或qwords)压入。
一个字节由8位,一个2字节的字(或16位),一个2字的双字(或32位)和一个2字的双字(或64位)组成。这些不是排列位的唯一方法。通常在SIMD指令中也会发生128位和256位操作。
汇编指令在寄存器上运行,而存储器地址通常以上述形式之一运行。
ALU(算术逻辑单元)对这样的位束进行操作,就好像它们表示整数(通常是二进制补码格式),而对FPU进行操作就好像它们在哪里是浮点值(通常是IEEE 754样式float
和double
)。其他部分的作用就像是捆绑了某些格式,字符,表条目,CPU指令或地址的数据一样。
在典型的64位计算机上,8字节(64位)的捆绑包是地址。我们通常以十六进制格式(如0xabcd1234cdef5678
)显示这些地址,但这只是人类读取位模式的一种简便方法。每个字节(8位)被写为两个十六进制字符(等效地,每个十六进制字符-0至F-代表4位)。
实际发生的情况(实际上是某种程度上)是有些位通常存储在寄存器中或存储在存储库中的相邻位置中,而我们只是试图将其描述给另一个人。
跟随指针包括要求内存控制器在该位置提供一些数据。您通常会在某个位置(好吧,隐式地包含一系列位置,通常是连续的)向内存控制器询问一定数量的字节,并且它是通过各种我不会介绍的机制来传递的。
该代码通常指定要获取的数据的目的地-寄存器,另一个内存地址等-通常将浮点数据加载到期望整数的寄存器中是个坏主意,反之亦然。
C / C ++中的数据类型是编译器跟踪的内容,它会更改生成的代码。通常,数据中没有任何内在因素使它实际上成为任何一种类型。只是位的集合(按字节排列),它们由代码以类似整数的方式(或类似浮点的方式或类似地址的方式)进行操作。
也有例外。有架构,其中的某些东西是不同类型的位。最常见的示例是受保护的执行页面-告诉CPU做什么的指令是位,而在运行时,包含要执行的代码的(内存)页面被特别标记,无法修改,并且您无法执行未标记的页面作为执行页面。
还存在只读数据(有时无法物理写入ROM中存储!),对齐问题(某些处理器无法double
从内存加载s,除非它们以特定方式对齐,或者需要特定对齐的SIMD指令),以及无数的其他架构怪癖。
甚至以上的细节水平也是一个谎言。计算机并不是“真正”推动位,而是推动电压和电流。这些电压和电流有时不执行其在比特抽象级别上“应该”执行的操作。这些芯片旨在检测大多数此类错误并进行纠正,而无需更高级别的抽象。
即使那是谎言。
每个抽象级别都隐藏了下面的一个级别,使您无需考虑Feynman图以打印出来就可以考虑解决问题"Hello World"
。
因此,在足够的诚实度下,计算机会推送位,并且通过使用这些位来赋予这些位含义。
人们对于指针是否为整数有很多疑问。这些问题实际上有答案。但是,您将不得不踏入规范之地,这并非出于胆小者的考虑。我们将看一下C规范ISO / IEC 9899:TC2
6.3.2.3指针
整数可以转换为任何指针类型。除非先前指定,否则结果是实现定义的,可能未正确对齐,可能未指向引用类型的实体,并且可能是陷阱表示。
任何指针类型都可以转换为整数类型。除非先前指定,否则结果是实现定义的。如果结果不能用整数类型表示,则行为是不确定的。结果不必在任何整数类型的值范围内。
现在,您需要了解一些常见的规范术语。“实现定义”是指每个编译器都可以不同地定义它。实际上,根据您的编译器设置,编译器甚至可能以不同的方式定义它。未定义的行为意味着允许编译器执行任何操作,从给出编译时错误到无法解释的行为,再到完美运行。
由此可见,除了可能会转换为整数类型外,未指定底层存储形式。现在说实话,几乎每个在阳光下的编译器都将引擎盖下的指针表示为整数地址(在少数特殊情况下,它可能表示为2个整数,而不仅仅是1个),但是该规范绝对允许任何东西,例如表示地址为10个字符串!
如果我们跳出C语言并仔细阅读C ++规范,可以通过使用更加清晰reinterpret_cast
,但这是一种不同的语言,因此它对您的价值可能会有所不同:
ISO / IEC N337:C ++ 11草案规范(我只有手头的草案)
5.2.10重新解释演员
指针可以显式转换为任何足以容纳它的整数类型。映射功能是实现定义的。[注:对于那些了解底层机器的寻址结构的人来说,这并不奇怪。—尾注] std :: nullptr_t类型的值可以转换为整数类型;该转换与(void *)0转换为整数类型具有相同的含义和有效性。[注意:reinterpret_cast不能用于将任何类型的值转换为std :: nullptr_t类型。—尾注]
整数类型或枚举类型的值可以显式转换为指针。指针将转换为足够大的整数(如果实现中存在这样的大小)并返回相同的指针类型,则指针将具有其原始值;指针和整数之间的映射否则由实现定义。[注:除非3.7.4.3中有描述,否则这种转换的结果将不是安全得出的指针值。—尾注]
正如您在此处看到的那样,C ++经历了几年的发展,可以安全地假设存在到整数的映射,因此不再谈论不确定的行为(尽管第4部分和第4部分之间存在有趣的矛盾)。 5的措辞为“如果实现中存在任何此类短语”)
现在您应该从中拿走什么?
最好的选择:强制转换为(char *)。C和C ++规范充满了指定数组和结构的打包的规则,并且都始终允许将任何指针转换为char *。char始终为1个字节(在C中不能保证,但是到C ++ 11为止,它已成为该语言的强制性组成部分,因此假设在任何地方均为1个字节都是相对安全的)。这允许您在逐字节级别上执行一些指针算术,而无需实际需要知道特定于实现的指针表示。
char *
?我正在考虑一个假设的机器,该机器具有用于代码和数据的单独地址空间。
char
在C中始终为1个字节。根据C标准:“ sizeof运算符得出其操作数的大小(以字节为单位)”和“将sizeof应用于具有char,unsigned char或signed char类型的操作数时, (或其限定版本),结果为1。” 也许您认为一个字节长8位。不一定是这样。为了符合该标准,一个字节必须至少包含8位。
有关C和C ++的重要一点是实际上是什么类型。它们真正所做的只是向编译器指示如何解释一组位/字节。让我们从以下代码开始:
int var = -1337;
根据体系结构,通常会给整数一个32位空间来存储该值。这意味着存储var的内存空间看起来像“ 11111111 11111111 11111010 11000111”或十六进制“ 0xFFFFFAC7”。而已。那就是所有存储在该位置的内容。所有类型的工作都是告诉编译器如何解释该信息。指针没有什么不同。如果我做这样的事情:
int* var_ptr = &var; //the ampersand is telling C "get the address where var's value is located"
然后,编译器将获取var的位置,然后以与第一个代码段保存值-1337相同的方式存储该地址。它们的存储方式和使用方式没有什么不同。甚至我使var_ptr成为一个指向int的指针也没关系。如果您愿意,可以这样做。
unsigned int var2 = *(unsigned int*)var_ptr;
这会将上面的var十六进制值(0xFFFFFAC7)复制到存储var2值的位置。如果要使用var2,则会发现该值为4294965959。var2中的字节与var相同,但数值不同。编译器对它们的解释有所不同,因为我们告诉它那些位表示无符号长整数。您也可以对指针值执行相同的操作。
unsigned int var3 = (unsigned int)var_ptr;
在此示例中,您最终将表示var地址的值解释为无符号int。
希望这可以为您澄清事物,并让您更好地了解C的工作原理。请注意,您不应在实际生产代码中做以下两行中我所做的任何疯狂的事情。那只是为了示范。
类型组合。
特别是,某些类型组合在一起,就好像它们是使用占位符参数化的一样。数组和指针类型如下:它们有一个这样的占位符,分别是数组元素的类型或所指向的对象。函数类型也是如此。它们可以为参数使用多个占位符,并为返回类型使用占位符。
声明为保留指向char的指针的变量的类型为“ char的指针”。声明为保留指向int的指针的变量的类型为“指向int的指针”。
可以通过取消引用操作将“指向int的指针”(的值)类型更改为“指向int的指针”。因此,类型的概念不仅是单词,而且是数学上有意义的结构,它决定了我们可以使用该类型的值做什么(例如取消引用,作为参数传递或分配给变量;它还决定了类型的大小(字节数))索引,算术和增量/减量操作)。
PS:如果您想深入了解类型,请尝试以下博客:http : //www.goodmath.org/blog/2015/05/13/expressions-and-arity-part-1/