指针在C语言中保存的数据的“类型”是什么?


30

我知道指针保存地址。我知道,指针的类型基于它们指向的数据的“类型”而被“一般地”知道。但是,指针仍然是变量,它们所持有的地址必须具有数据“类型”。根据我的信息,地址为十六进制格式。但是,我仍然不知道该十六进制是什么“数据”类型。(请注意,我知道十六进制是什么,但是10CBA20例如,当您说这是一个字符串吗?整数吗?是什么?当我想访问该地址并对其进行操作..本身时,我需要知道其类型。这这就是为什么我问。)


17
指针不是变量,而是。变量保存值(如果变量的类型是指针类型,则该值是指针,并且可能是包含有意义内容的内存区的地址)。给定的存储区可用于保存不同类型的各种值。
Basile Starynkevitch 2015年

29
“地址采用十六进制格式”不,这仅仅是调试器或库格式化位。使用相同的参数,您可以说它们是二进制或八进制的。
usr

您最好问一下格式,而不是类型。因此,下面有一些滑雪道答案...(尽管Kilian的答案很明确)。
莫妮卡(Monica)

1
我认为这里更深的问题是OP对type的理解。归结为它,您在程序中操作的值只是内存中的位。类型是程序员告诉编译器在生成汇编代码时如何对待这些位的方式。
贾斯汀·拉迪诺伊斯

我想现在用所有这些答案进行编辑为时已晚,但是如果您限制了硬件和/或操作系统,例如“在x64 Linux上”,那么这个问题会更好。
海德2015年

Answers:


64

指针变量的类型是..指针。

在C语言中,您被正式允许执行的操作是将其与其他指针(或特殊的NULL /零值)进行比较,添加或减去整数或将其强制转换为其他指针。

接受未定义的行为后,您可以查看实际的值。它通常是一个机器字,与整数一样,并且通常可以无损地与整数类型进行转换。(很多Windows代码通过在DWORD或HANDLE typedef中隐藏指针来完成此操作)。

在某些体系结构中,指针不简单,因为内存不平坦。DOS / 8086``近''和``远''; PIC的不同内存和代码空间。


2
您也可以采用两个指针之间的差值p1-p2。结果是带符号的整数值。特别是&(array[i])-&(array[j]) == i-j
MSalters 2015年

13
实际上,还指定了向整数类型的转换,特别是对整数类型的转换,intptr_tuintptr_t保证对于指针值“足够大”。
Matthieu M.

3
您可以依靠转换来工作,但是整数和指针之间的映射是实现定义的。(唯一的例外是0-> null,并且仅在0是常量IIRC时才指定。)
cHao 2015年

7
p如果在c中实现依赖行为,则在printf中添加说明符将使人可读的空指针表示形式定义为a。
dmckee 2015年

6
这个答案有一个大致正确的想法,但对特定的主张却没有。将指针强制转换为整数类型不是未定义的行为,Windows HANDLE数据类型也不是指针值(它们不是隐藏在整数数据类型中的指针,它们是隐藏在指针类型中的整数,以防止算术)。
Ben Voigt 2015年

44

您太复杂了。

地址只是整数,句点。理想情况下,它们是引用的存储单元的数量(实际上,由于段,虚拟内存等原因,这会变得更加复杂)。

十六进制语法是一种完整的小说,仅为了方便程序员而存在。0x1A和26是完全相同的数字,并且类型完全相同,计算机也不使用-内部,计算机始终使用00011010(一系列二进制信号)。

编译器是否允许您指针视为数字取决于语言定义-传统上,“系统编程”语言对事物的工作方式更加透明,而“高级”语言则更经常尝试隐藏裸机从程序员那里得到的-但这并没有改变指针是数字的事实,指针通常是数字的最常见类型(一种与您的处理器体系结构位数一样多的数字)。


26
地址绝对不是整数。就像浮点数绝对不是整数一样。
gnasher729 2015年

8
确实。最著名的反例是Intel 8086,其中的指针是两个整数。
MSalters 2015年

5
@Rob在分段内存模型中,指针可以是隐含该分段的单个值(相对于分段起点的地址;偏移量),也可以是分段/选择器和偏移量。(我认为Intel使用了“选择器”一词;我懒得查找它。)在8086上,它们被表示为两个16位整数,这些整数组合起来形成一个20位物理地址。(是的,如果您倾向于这样的话,可以用很多很多不同的方式寻址同一个存储单元:address =(segment << 4 + offset)&0xfffff。)当在实模式下运行时,这会在所有x86兼容版本中继续进行。
CVn 2015年

4
作为一个长期的汇编程序程序员,我可以证明计算机的内存不过是保存整数的内存位置。但是,重要的是您如何对待它们并跟踪那些整数表示什么。例如,在我的系统上,十进制数字4075876853被存储为x'F2F0F1F5',这是EBCDIC中的字符串'2015'。十进制2015将存储为000007DF,而x'0002015C'表示十进制2015,为压缩十进制格式。作为汇编程序员,您必须跟踪这些情况。编译器针​​对HL语言执行此操作。
史蒂夫·艾夫斯

7
地址可以与整数一一对应,但是计算机上的其他所有内容也可以:)
hobbs 2015年

15

指针就是-指针。没有别的了。不要试图认为这是另外一回事。

在C,C ++和Objective-C等语言中,数据指针具有四种可能的值:

  1. 指针可以是对象的地址。
  2. 指针可以指向数组的最后一个元素。
  3. 指针可以是空指针,这意味着它没有指向任何东西。
  4. 指针可以具有不确定的值,换句话说,它是垃圾,如果尝试使用它,可能会发生任何事情(包括坏事)。

还有一些函数指针,它们标识一个函数,或者为空函数指针,或者具有不确定的值。

其他指针是C ++中的“成员指针”。这些绝对不是内存地址!相反,它们标识类的任何实例的成员。在Objective-C中,您具有选择器,这些选择器类似于“指向具有给定方法名称和参数名称的实例方法的指针”。像成员指针一样,它标识所有类的所有方法,只要它们看起来相同即可。

您可以研究特定的编译器如何实现指针,但这是一个完全不同的问题。


4
有指向函数的指针,在C ++中有指向成员的指针。
sdenham

指向成员的C ++指针不是内存地址吗?当然可以。class A { public: int num; int x; }; int A::*pmi = &A::num; A a; int n = a.*pmi;如果变量pmi不包含内存地址(即,如代码的最后一行所确定的那样,则为class num的实例成员的地址),则该变量将没有太大用处。您可以将其强制转换为普通指针(尽管编译器可能会警告您)并成功取消引用(证明它是任何其他指针的语法糖)。aAint
dodgethesteamroller 2015年

9

指针是一种位模式,用于寻址(用于读取或写入目的的唯一标识)RAM中的存储字。出于历史和传统原因,更新单位为八位,英文为“字节”,法文为“八位位组”。这是普遍存在的,但不是固有的;其他尺寸已经存在。

如果我没记错的话,那是一台使用29位字的计算机。这不仅不是二的幂,甚至是素数。我以为这是SILLIAC,但相关的Wikipedia文章不支持这一点。CAN BUS使用29位地址,但是按照惯例,即使它们在功能上相同,也不会将其称为指针。

人们一直断言指针是整数。这既不是内在的也不是必需的,但是如果我们将位模式解释为整数,则通常会出现有用的质量,从而可以非常直接(因此在小型硬件上非常有效)实现诸如“字符串”和“数组”之类的结构。连续内存的概念取决于顺序的邻接关系,并且可以进行相对定位。整数比较和算术运算可以有意义地应用。因此,用于存储寻址的字长与ALU(进行整数数学运算的东西)之间几乎始终具有很强的相关性。

有时两者并不对应。在早期的PC中,地址总线为24位宽。


Nitpick,在当今常见的操作系统中,指针标识虚拟内存中的位置,与物理RAM字没有直接关系(如果虚拟内存位置在操作系统已知为全零的内存页中,则物理上甚至可能不存在) )。
海德

@hyde-您的论点显然在您明确打算使用的情况下很有用,但计算机的主要形式是嵌入式控制器,其中虚拟内存等奇迹是部署受限的最新创新。另外,您所指出的内容绝不会帮助OP理解指针。我认为某些历史背景会使这一切变得没有那么随意。
Peter Wone 2015年

我不知道谈论RAM是否可以帮助OP理解,因为问题特别是关于真正的指针。哦,根据定义,c指针中的另一个nitpick 指向字节(可以安全地强制转换为char*例如用于内存复制/比较的目的,并且sizeof char==1由C标准定义),而不是字(除非CPU字的大小与字节的大小相同)。
海德2015年

指针本质上是用于存储的哈希键。这是语言和平台不变的。
Peter Wone 2015年

问题是关于c 指针的。指针肯定不是哈希键,因为没有哈希表,也没有哈希算法。它们自然是某种地图/字典键(用于“地图”的足够广泛的定义),但不是哈希键。
海德

6

基本上,每台现代计算机都是一台按位按钮的计算机。通常,它会将数据簇中的位(字节,字,dwords或qwords)压入。

一个字节由8位,一个2字节的字(或16位),一个2字的双字(或32位)和一个2字的双字(或64位)组成。这些不是排列位的唯一方法。通常在SIMD指令中也会发生128位和256位操作。

汇编指令在寄存器上运行,而存储器地址通常以上述形式之一运行。

ALU(算术逻辑单元)对这样的位束进行操作,就好像它们表示整数(通常是二进制补码格式),而对FPU进行操作就好像它们在哪里是浮点值(通常是IEEE 754样式floatdouble)。其他部分的作用就像是捆绑了某些格式,字符,表条目,CPU指令或地址的数据一样。

在典型的64位计算机上,8字节(64位)的捆绑包是地址。我们通常以十六进制格式(如0xabcd1234cdef5678)显示这些地址,但这只是人类读取位模式的一种简便方法。每个字节(8位)被写为两个十六进制字符(等效地,每个十六进制字符-0至F-代表4位)。

实际发生的情况(实际上是某种程度上)是有些位通常存储在寄存器中或存储在存储库中的相邻位置中,而我们只是试图将其描述给另一个人。

跟随指针包括要求内存控制器在该位置提供一些数据。您通常会在某个位置(好吧,隐式地包含一系列位置,通常是连续的)向内存控制器询问一定数量的字节,并且它是通过各种我不会介绍的机制来传递的。

该代码通常指定要获取的数据的目的地-寄存器,另一个内存地址等-通常将浮点数据加载到期望整数的寄存器中是个坏主意,反之亦然。

C / C ++中的数据类型是编译器跟踪的内容,它会更改生成的代码。通常,数据中没有任何内在因素使它实际上成为任何一种类型。只是位的集合(按字节排列),它们由代码以类似整数的方式(或类似浮点的方式或类似地址的方式)进行操作。

也有例外。有架构,其中的某些东西是不同类型的位。最常见的示例是受保护的执行页面-告诉CPU做什么的指令是位,而在运行时,包含要执行的代码的(内存)页面被特别标记,无法修改,并且您无法执行未标记的页面作为执行页面。

还存在只读数据(有时无法物理写入ROM中存储!),对齐问题(某些处理器无法double从内存加载s,除非它们以特定方式对齐,或者需要特定对齐的SIMD指令),以及无数的其他架构怪癖。

甚至以上的细节水平也是一个谎言。计算机并不是“真正”推动位,而是推动电压和电流。这些电压和电流有时不执行其在比特抽象级别上“应该”执行的操作。这些芯片旨在检测大多数此类错误并进行纠正,而无需更高级别的抽象。

即使那是谎言。

每个抽象级别都隐藏了下面的一个级别,使您无需考虑Feynman图以打印出来就可以考虑解决问题"Hello World"

因此,在足够的诚实度下,计算机会推送位,并且通过使用这些位来赋予这些位含义。


3

人们对于指针是否为整数有很多疑问。这些问题实际上有答案。但是,您将不得不踏入规范之地,这并非出于胆小者的考虑。我们将看一下C规范ISO / IEC 9899:TC2

6.3.2.3指针

  1. 整数可以转换为任何指针类型。除非先前指定,否则结果是实现定义的,可能未正确对齐,可能未指向引用类型的实体,并且可能是陷阱表示。

  2. 任何指针类型都可以转换为整数类型。除非先前指定,否则结果是实现定义的。如果结果不能用整数类型表示,则行为是不确定的。结果不必在任何整数类型的值范围内。

现在,您需要了解一些常见的规范术语。“实现定义”是指每个编译器都可以不同地定义它。实际上,根据您的编译器设置,编译器甚至可能以不同的方式定义它。未定义的行为意味着允许编译器执行任何操作,从给出编译时错误到无法解释的行为,再到完美运行。

由此可见,除了可能会转换为整数类型外,未指定底层存储形式。现在说实话,几乎每个在阳光下的编译器都将引擎盖下的指针表示为整数地址(在少数特殊情况下,它可能表示为2个整数,而不仅仅是1个),但是该规范绝对允许任何东西,例如表示地址为10个字符串!

如果我们跳出C语言并仔细阅读C ++规范,可以通过使用更加清晰reinterpret_cast,但这是一种不同的语言,因此它对您的价值可能会有所不同:

ISO / IEC N337:C ++ 11草案规范(我只有手头的草案)

5.2.10重新解释演员

  1. 指针可以显式转换为任何足以容纳它的整数类型。映射功能是实现定义的。[注:对于那些了解底层机器的寻址结构的人来说,这并不奇怪。—尾注] std :: nullptr_t类型的值可以转换为整数类型;该转换与(void *)0转换为整数类型具有相同的含义和有效性。[注意:reinterpret_cast不能用于将任何类型的值转换为std :: nullptr_t类型。—尾注]

  2. 整数类型或枚举类型的值可以显式转换为指针。指针将转换为足够大的整数(如果实现中存在这样的大小)并返回相同的指针类型,则指针将具有其原始值;指针和整数之间的映射否则由实现定义。[注:除非3.7.4.3中有描述,否则这种转换的结果将不是安全得出的指针值。—尾注]

正如您在此处看到的那样,C ++经历了几年的发展,可以安全地假设存在到整数的映射,因此不再谈论不确定的行为(尽管第4部分和第4部分之间存在有趣的矛盾)。 5的措辞为“如果实现中存在任何此类短语”)


现在您应该从中拿走什么?

  • 指针的确切表示形式由实现定义。(实际上,为了使其更混乱,一些小型嵌入式计算机将空指针(void)0表示为地址255,以支持它们使用的某些地址别名技巧)*
  • 如果您必须询问指针在内存中的表示形式,那么您可能不在编程生涯中的某个时候想要摆弄它们。

最好的选择:强制转换为(char *)。C和C ++规范充满了指定数组和结构的打包的规则,并且都始终允许将任何指针转换为char *。char始终为1个字节(在C中不能保证,但是到C ++ 11为止,它已成为该语言的强制性组成部分,因此假设在任何地方均为1个字节都是相对安全的)。这允许您在逐字节级别上执行一些指针算术,而无需实际需要知道特定于实现的指针表示。


您是否可以将函数指针强制转换为char *?我正在考虑一个假设的机器,该机器具有用于代码和数据的单独地址空间。
菲利普·肯德尔2015年

@PhilipKendall好点。我没有在规范中包含该部分,但是由于恰恰引发了问题,因此函数指针与规范中的数据指针完全不同。成员指针的处理方式也有所不同(但它们的行为也有很大不同)
Cort Ammon-恢复莫妮卡

A char在C中始终为1个字节。根据C标准:“ sizeof运算符得出其操作数的大小(以字节为单位)”和“将sizeof应用于具有char,unsigned char或signed char类型的操作数时, (或其限定版本),结果为1。” 也许您认为一个字节长8位。不一定是这样。为了符合该标准,一个字节必须至少包含8位。
David Hammen 2015年

该规范描述了指针和整数类型之间的转换。应该始终牢记,类型之间的“转换”并不意味着类型相等,甚至不意味着内存中这两种类型的二进制表示将具有相同的位模式。(可以将ASCII“转换”为EBCDIC。可以将大字节序“转换”为小字节序。)
user2338816 2015年

1

在大多数体系结构中,一旦将指针翻译为机器代码,指针的类型就不复存在了(也许“胖指针”除外)。因此,指向inta的指针double至少与指向a的指针是无法区分的。*

[*]虽然,您仍然可以根据要应用的操作种类进行猜测。


1

有关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的工作原理。请注意,您不应在实际生产代码中做以下两行中我所做的任何疯狂的事情。那只是为了示范。


1

整数。

计算机中的地址空间从0开始按顺序编号,并以1递增。因此,指针将保存一个与地址空间中的地址相对应的整数。


1

类型组合。

特别是,某些类型组合在一起,就好像它们是使用占位符参数化的一样。数组和指针类型如下:它们有一个这样的占位符,分别是数组元素的类型或所指向的对象。函数类型也是如此。它们可以为参数使用多个占位符,并为返回类型使用占位符。

声明为保留指向char的指针的变量的类型为“ char的指针”。声明为保留指向int的指针的变量的类型为“指向int的指针”。

可以通过取消引用操作将“指向int的指针”(的值)类型更改为“指向int的指针”。因此,类型的概念不仅是单词,而且是数学上有意义的结构,它决定了我们可以使用该类型的值做什么(例如取消引用,作为参数传递或分配给变量;它还决定了类型的大小(字节数))索引,算术和增量/减量操作)。

PS:如果您想深入了解类型,请尝试以下博客:http : //www.goodmath.org/blog/2015/05/13/expressions-and-arity-part-1/

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.