【问题标题】:What is happening when casting to a pointer type in C?在 C 中转换为指针类型时会发生什么?
【发布时间】:2017-06-20 18:27:51
【问题描述】:

我已经看到 NULL 相当于(void*)0。但我不明白为什么需要将零类型转换为void*。当我们做这样的事情时,真正发生在幕后的是什么

int *p = (int*)10;

上述语句中(int*)是否将整数10(占用4字节)的地址空间扩展为8字节?

【问题讨论】:

  • @PatrickRoberts:错了!
  • 你不会在这个网站上得到一个很好的初学者对指针的解释,因为它不适合答案框,即使有人想写一个。充其量你会得到一个过于迂腐的答案,让你比以前更加困惑。去你友好的当地公共图书馆,向图书馆员索要一本介绍性的 C 教科书。不管是哪一个。
  • @PatrickRoberts:请提供对标准的参考。整数到指针的转换是实现定义的。通常它是绝对地址,而不是相对于程序(哪个部分?.data.bss.text?)并显示“垃圾”的定义,标准中没有类似的东西。实际上,取消引用不指向对象的指针会调用 UB。这就是我们可以从给出的稀疏信息中假设的全部内容。
  • @PatrickRoberts:链接代码 golf 调用 UB,是纯垃圾代码。编写这样的代码是立即被解雇的一个很好的理由。它是不可调试的。甚至不要考虑为您打算实际使用的程序编写这样的代码。
  • @zwol:很好的说法,但“哪一个都没有关系”是有问题的。 1) 图书馆的书架上有 K&R-C Rev. 1 的食物。 2)也有新的书籍教授不好的做法,并且在某些方面完全是错误的。尤其是在指针等方面,有很多“它对我有用,所以它是正确的”伏都教。

标签: c pointers casting


【解决方案1】:

有几种方法可以回答这个问题。

我们说指针值是内存位置的地址。但是不同的计算机对内存使用了不同的寻址方案。 C 是一种高级语言,可在多种计算机上移植。 C 不要求特定的内存架构。就 C 编程语言而言,内存地址实际上可以是“第四大道 123 号”之类的东西,很难想象在整数和这样的地址之间来回转换。

现在,对于您可能使用的任何机器,内存实际上是以一种相当简单且不足为奇的方式线性寻址的。如果你的程序有 1000 字节的可用内存,那么这些字节的地址范围可能从 0 到 999。所以如果你说

char *cp = (char *)10;

您只是设置了一个指向位于地址 10 的字节(或者,即程序地址空间中的第 11 个字节)的指针。

现在,在 C 语言中,指针不仅仅是内存中某个位置的原始地址。在 C 中,还声明了一个指针来指定它指向的数据类型。所以如果我们说

int *ip = (int *)10;

我们正在设置一个指向位于地址 10 的 int 值数据的指针。它与 cp 指向的内存中的点相同,但由于它是一个 int 指针,它将访问一个 int 值的字节,没有一个字节像cp 那样。如果我们在一台旧的 16 位机器上,并且 int 是两个字节,我们可以认为 ip 指向我们地址空间中的第五个 int。

C 中的强制转换实际上可以做两件事:(1) 转换一个值(“更改位”),或 (2) 更改一个值的解释。如果我们说float f = (float)3;,我们将在 3 的整数表示和 3 的浮点表示之间进行转换,这可能是完全不同的。如果我们朝另一个方向前进,比如int i = (int)3.14;,我们也会丢弃小数部分,所以会有更多的转换。但是如果我们说int *ip = (int *)10;,我们实际上并没有对值 10 做任何事情,我们只是将它重新解释为一个指针。如果我们说char *cp = (char *)ip,我们又没有改变任何东西,我们只是重新解释了一种不同的指针。

不过,我赶紧补充一下,我在这里所说的关于指针转换的所有内容都是 (a) 非常低级且依赖于机器的,并且 (b) 不是普通 C 程序员应该具备的那种东西在普通的编程任务中考虑,以及 (c) C 语言不保证。

特别是,即使在为具有传统线性寻址内存模型的计算机编程时,您的程序也可能无法访问地址 10,因此这些指针(cpip)可能是非常没用,如果您尝试使用它们可能会产生异常。 (另外,当我们有一个像 ip 这样指向超过 1 个字节的指针时,就会出现它指向哪些字节的问题。如果 ip 是 10,它可能指向 16 位上的字节 10 和 11 ,字节寻址的机器,但是这两个字节中哪个是int的低位,哪个是高位?这取决于它是“big endian”还是“little endian”机器。)

然后我们来到空指针。当您使用常量“0”作为指针值时,情况会有所不同。如果你说

void *p = (void *)0;

严格来说,您并不是在说“使p 指向地址0”。相反,您是在说“使 p 成为空指针”。但事实证明这与强制转换无关,这是因为语言中的一种特殊情况:在指针上下文中,常量 0 表示 空指针常量

空指针是一个特殊的指针值,它被定义为不指向任何地方。它可能在内部表示为指向地址 0 的指针,也可能以其他方式表示。 (如果它实际上表示为指向地址 0 的指针,您的编译器会小心安排在地址 0 处永远不会有任何实际数据,因此即使指针指向地址 0,指针“不指向任何地方”仍然是正确的. 这有点令人困惑,对此感到抱歉。)

虽然指向像10 这样的原始地址的指针是低级的、危险的并且依赖于机器,但空指针是定义明确的并且非常好。例如,当您调用malloc 并且它无法为您提供所需的内存时,它会返回一个空指针来告诉您。当您测试malloc 的返回值以查看它是成功还是失败时,您只需检查它是否给了您一个空指针,并且没有任何低级、不可移植或不鼓励这样做。

有关这一切的更多信息,请参阅http://c-faq.com/null/index.html

【讨论】:

  • char *cp = (int *)10; - 没有错字?
  • "... 或 (2) 更改值的解释" - 实际上没有。它总是一个转换。 C 没有 reinterpret_cast 像 C++ 介绍的那样。通过指针转换的典型重新解释会调用未定义的行为(其中对齐是问题的最小部分),很少有例外。因此通过union 明确允许别名 - 因为这是唯一合法的方式。 空指针也不一定是单个值;一种替代方法是在指针中设置一个位(这实际上有助于调试代码)。
  • @Olaf 在绝大多数流行机器上,char *cp = (char *)ip 在从ipcp 时不会改变任何位,这当然是我所说的不转换的意思。
  • 这仍然是一个转换:6.3.2.3p7 和相关。位是否改变无关紧要。 1:1 复制仍然是一种转换。
  • 还有一个错字int *ip = (int)10;
【解决方案2】:

指针是字节的短排列。通过强制转换,C 允许假装这些字节代表一个整数。

Type* p = ...;
intptr_t i = (intptr_t)p;

当您需要将指针传递给期望整数的接口时,这偶尔(但很少)有用。要恢复指针,只需反转转换即可。

Type* recovered_p = (Type*)i;

这不会分配任何内存。如果i 包含的字节如果被视为Type*,则只能引用recovered_p,则引用先前分配的Type 值。这意味着以下内容不会产生可用的指针:

int *p = (int*)10;

使用整数存储指针的示例。

typedef void (*Visitor)(intptr_t, ListNode*);

void List_visit(List* list, Visitor visitor, intptr_t arg) {
   for (ListNode* node = list->head; node; node=node->next) {
      visitor(arg, node);
   }
}

void printer(intptr_t arg, ListNode* node) {
   State* state = (intptr_t)arg;
   printf("%*s%s\n", ( state->count++ )*2, "", node->value);
}

int main(void) {
   List* list = ...;
   State* state = ...;
   List_visit(list, printer, (intptr_t)state);
   List_free(list);
   State_free(state);
   return 0;
}

【讨论】:

  • 这是另一个使用它的示例:用 C 编写的 Perl 扩展通常需要处理由 C 库创建的句柄/指针。这些指针需要转换为 Perl 领域中存在的东西,因此它们被转换为整数(在扩展类的 Perl 对象内部),以便转换为指针并供以后对 C 库的调用使用。跨度>
  • @jschultz410,感谢您的修复,但请不要将您的 personal style 强加给其他人。
  • 要考虑的事情(请不要回复,我不想引发风格大战):int *p, i - *p is a declarator, the *` 语法上语义上属于p,而不是类型说明符。因此int* p, i; 会在视觉上产生误导。虽然在语法上无关紧要,尤其是对于初学者来说,这样的细节很烦人(实际上不仅对初学者来说)。
  • @Olaf,我完全同意如果一个变量是指针,一次声明多个变量是个坏主意。
  • @ikegami:这就像用马将汽车拉到安全燃料而不是使用马车(使用马时更合适)。它是完全有效的,通用语法在视觉上很好地支持它。
猜你喜欢
  • 2018-05-16
  • 2018-02-18
  • 2021-09-20
  • 2016-04-17
  • 1970-01-01
  • 2020-12-10
  • 1970-01-01
  • 2013-09-23
相关资源
最近更新 更多