C教程 - 对`int i = *(int *)&s;`的疑惑

5

正在学习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个字节吗?
从左边的2个字节开始读取s。在这种情况下,如果我不知道它正在读取什么,这将是非常危险的,因为short类型只分配了2个字节。
因为我没有分配/拥有的内存,这是否不应该崩溃?

6
停止阅读那份教程。那种东西确实是不好的。 - Mat
3
哪个教程?那我们就把它放到我们的心理黑名单上。 - Jonathan Leffler
6个回答

5
  1. 永远不要这样做。
  2. 如果教程教授/宣扬这种方法,请将其丢弃。

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

事实上,这是危险的,违反了Strict Aliasing Rule[详见下文],并导致未定义行为
编译器应该会给出这样的警告。

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

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


[详细信息]

严格别名是C(或C ++)编译器做出的一种假设,即对不同类型的对象进行解引用指针永远不会引用相同的内存位置(即别名彼此)。

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


3
首先,绝不要这样做
至于为什么程序不会崩溃:由于变量 s 是一个本地变量,它被分配在堆栈上。如果在您的体系结构中 shortint 有不同的大小(这并不是必须的),那么您很可能会从与堆栈相同的内存页中读取几个字节;因此,不会出现访问冲突(即使您会读取到垃圾数据)。 可能如此。

1

基本上你是对的,因为你正在访问一个 int* 指针,这将获取4个字节,而不是只有2个字节被保留给 's' 存储。结果内容不能完美地反映出 's' 的真实含义。

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

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

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

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

在这种情况下,您的程序更有可能崩溃,因为您开始覆盖数据。根据您正在操作的数据,其影响可能从无害的程序不当行为到程序崩溃不等,例如,如果您覆盖了推送到堆栈中的返回地址... 对我来说,数据损坏是最难(如果不是最难)处理的错误类别之一,因为其影响可以随机地以非确定性模式影响您的系统,并且可能发生在实际执行原始有问题指令之后很长时间。

如果您想了解更多关于内存管理方面的信息,您可能需要查阅操作系统设计中的虚拟内存管理

希望对您有所帮助,


1

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

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


1

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


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

不能保证所有指针类型的大小都相同。在特定的架构上可能是这样,但您不能依赖它普遍成立。永远不要做出这种假设。 - John Bode
有一些特殊的例子,其中函数指针在大小上与其他指针不同,在某些平台上,指向不同类型的指针具有不同的大小吗?尽管标准允许更糟糕的情况...唯一的保证是 sizeof(void*) 足够大以容纳任何其他非函数指针类型。 - dmckee --- ex-moderator kitten
这似乎很奇怪,为什么要使用短整型和8字节字符?这是通用的吗?在哪个层面上?可能是编译器吗? - Mâtt Frëëman
@wtfcoder,您没有同时为short和char提供8个字节。指针包含一个内存地址,而地址大小应该与其所包含的类型无关(应该是这样吧?)。 - Henrique Rocha

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接