【问题标题】:C Tutorial - Wonder about `int i = *(int *)&s;`C 教程 - 想知道 `int i = *(int *)&s;`
【发布时间】:2011-09-18 16:35:00
【问题描述】:

完成 C 教程

#include <stdio.h>

int main() {
  short s = 10;
  int i = *(int *)&s; // wonder about this
  printf("%i", i);
  return 0;
}

当我告诉 C s 的地址是一个 int 时,它不应该读 4 个字节吗?

s 的2 个字节的最左边开始。在这种情况下,这不是非常危险,因为我不知道它在读取什么,因为 short 只分配了 2 个字节?

如果尝试访问我没有分配/不属于我的内存,这不会崩溃吗?

【问题讨论】:

  • 停止阅读该教程。这种事情确实不好
  • 哪个教程?然后我们可以把它放在我们的心理黑名单上。

标签: c


【解决方案1】:
  1. 永远不要这样做
  2. 如果教程教导/宣扬这一点,请丢弃它。

正如您所指出的,它会读取比实际分配的字节更多的字节,因此它会从您的变量未分配的内存中读取一些垃圾值。

事实上它很危险,它会破坏 Strict Aliasing Rule[详情如下] 并导致 未定义行为
编译器应该给你这样的警告。

warning: dereferencing type-punned pointer will break strict-aliasing rules

当你的编译器发出警告时,你应该始终倾听它。


[详情]

严格别名是由 C(或 C++)编译器做出的一个假设,即取消引用指向不同类型对象的指针永远不会引用相同的内存位置(即相互别名。)

该规则的例外是 char*,它允许指向任何类型。

【讨论】:

    【解决方案2】:

    首先,永远不要这样做

    至于为什么它不会崩溃:因为s 是本地的,所以它是在堆栈上分配的。如果 shortint 在您的架构中具有不同的大小(这不是给定的),那么您可能最终会从与堆;所以不会有访问冲突(即使你会读垃圾)。

    可能。

    【讨论】:

      【解决方案3】:

      正如您所说,这是危险且未定义的行为。

      它不会在 32(或 64)位平台上崩溃的原因是大多数编译器为每个堆栈变量分配至少 32 位。这使得访问速度更快,但在例如8 位处理器你会得到高位的垃圾数据。

      【讨论】:

        【解决方案4】:

        不,它不会使您的程序崩溃,但是它将读取堆栈上的其他变量(或可能是垃圾)的一部分。我不知道你是从哪个教程得到的,但是那种代码很吓人。

        【讨论】:

          【解决方案5】:

          首先,所有地址的大小都相同,如果您在 64 位架构中,每个 char *、short * 或 int * 将有 8 个字节。 在 & 符号前使用星号会取消效果,因此 *&x 在语义上等同于 x。

          【讨论】:

          • 保证所有指针类型都具有相同的大小。在特定架构上可能是这种情况,但你不能相信它是普遍正确的。永远不要做出这样的假设。
          • 一些特殊的例子,其中函数指针的大小与Are there are any platforms where pointers to different types have different sizes? 中的其他指针不同,尽管标准允许更糟糕的情况......唯一的保证是sizeof (void*) 足够大以容纳任何其他指针非函数指针类型。
          • 给出一个短字符和 8 个字节似乎很奇怪?这是通用的,是什么级别的?大概是编译器?
          • @wtfcoder 你没有给 short 和 char 8 个字节。一个指针包含一个内存地址,地址大小(应该?)独立于它所包含的类型。
          【解决方案6】:

          基本上你是对的,因为你正在访问一个 int * 指针,这将获取 4 个字节,而不是为 's' 存储保留的唯一 2 个字节,并且生成的内容不会完美反映 ' s'的意思。

          但是这很可能不会崩溃,因为 's' 位于堆栈上,因此根据此时堆栈的布局方式,您很可能会读取在 'main' 函数序言期间推送的数据...

          如果程序由于无效的读取内存访问而崩溃,您需要访问未映射的内存区域,这将在用户世界级别触发“分段错误”,而在内核级别触发“页面错误”。 “映射”是指您在虚拟内存区域和物理内存区域之间有一个已知的映射(这种映射由操作系统处理)。这就是为什么如果你访问一个 NULL 指针你会得到这样的异常,因为在用户世界级别没有有效的映射。通常会通过调用类似 malloc() 的方法为您提供有效的映射(注意 malloc() 不是系统调用,而是管理虚拟内存块的智能包装器)。您的堆栈也不例外,因为它只是像其他任何东西一样的内存,但是已经为您完成了一些预映射区域,因此当您在块中创建局部变量时,您不必担心它的内存位置,因为它已处理您,在这种情况下,您访问的距离不够远,无法到达未映射的地方。

          现在假设你做了这样的事情:

          short s = 10;
          int *i = (int *)&s;
          *i = -1;
          

          那么在这种情况下,您的程序更有可能崩溃,因为在这种情况下您开始覆盖数据。根据您所接触的数据,其影响范围可能从无害的程序不当行为到程序崩溃,例如,如果您覆盖推送到堆栈中的返回地址......数据损坏对我来说是最难的(如果不是最难的) ) 要处理的错误类别,因为它的影响可能会以非确定性模式随机影响您的系统,并且可能在原始违规指令实际执行后很久才发生。

          如果您想了解有关内部内存管理的更多信息,您可能需要查看操作系统设计中的Virtual Memory Management

          希望对你有帮助,

          【讨论】:

            猜你喜欢
            • 2019-10-15
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-08-22
            • 1970-01-01
            • 2012-12-08
            • 2021-12-21
            • 1970-01-01
            相关资源
            最近更新 更多