问题

C 语言中不同类型指针的大小是否完全相同,为什么?

回答
在 C 语言中,不同类型指针的大小不一定完全相同,但绝大多数情况下是相同的。这是一个非常值得深入探讨的问题,背后涉及到计算机的底层原理和 C 语言的设计哲学。

要理解这一点,我们需要先明确几个概念:

1. 指针的本质: 无论指针指向的是 `int`、`char`、`float` 还是一个结构体,它本质上存储的是一个内存地址。这个内存地址指向程序内存中的某个特定位置。

2. 内存寻址: 计算机的内存被组织成一个个字节(byte),每个字节都有一个唯一的地址。CPU 通过这些地址来访问存储在内存中的数据。

3. 地址的表示: 地址本身是一个数值,用来唯一标识内存中的一个位置。为了能访问到内存中的任何一个字节,这个地址需要能够表示一个足够大的范围。

为什么大多数情况下,不同类型指针的大小是相同的?

绝大多数现代计算机系统都采用统一的内存寻址模型。这意味着,无论是 `int`、`char` 还是更复杂的类型,它们在内存中都占据一个地址。CPU 在访问内存时,并不关心它正在访问的是一个字节、一个字(word)还是一个更大的数据块,它只关心的是那个地址。

因此,为了能够访问到内存中的每一个字节,地址本身的表示能力决定了指针的大小。如果一个系统有 4GB 的内存,那么就需要一个能够表示从 0 到 4GB 1 的地址的数值类型。这个数值类型的大小就决定了指针的大小。

32 位系统: 在一个 32 位的系统中,CPU 的地址总线是 32 位的。这意味着它最多可以寻址 2^32 个字节的内存空间,也就是 4GB。因此,在 32 位系统上,无论是 `int`、`char`、`float` 还是 `struct mystruct`,它们的大小通常都是 4 字节。一个 `char` 指针能够指向内存中的任何一个字节,而一个 `int` 指针虽然指向的是一个 4 字节的整数,但它存储的仍然是那个整数的起始地址,这个地址本身的长度就是 4 字节。

64 位系统: 类似地,在 64 位的系统中,CPU 的地址总线是 64 位的。理论上可以寻址 2^64 字节的内存,这是一个天文数字。因此,在 64 位系统上,各种类型的指针大小通常都是 8 字节。

为什么说“不一定完全相同”,或者说是否存在例外情况?

虽然在绝大多数主流平台上指针大小是相同的,但 C 语言规范本身并没有强制规定所有指针类型的大小必须完全一致。这里存在一些技术上的可能性和历史遗留的原因,虽然在现代 C 编程中不常见,但理解它们有助于更全面地认识指针。

1. 特定体系结构或嵌入式系统: 在一些非常特殊的、或者老旧的体系结构中,可能存在不同寻址模式的指针。例如:
段式内存管理(Segmented Memory): 在早期的 x86 架构(如 DOS 时代使用的实模式)中,内存被划分为多个“段”。访问内存需要一个“段基址”和一个“偏移量”。不同类型的指针可能使用了不同的段/偏移量组合方式,导致它们的大小差异。例如,一个指向 ROM 的指针可能和指向 RAM 的指针在表示方式上有所不同。
“远指针”(Far Pointers)和“近指针”(Near Pointers): 在段式内存模型下,区分了“近指针”(只包含偏移量,依赖于默认的段)和“远指针”(包含段和偏移量)。远指针通常比近指针更大。
某些嵌入式系统: 在资源极其受限的微控制器上,可能为了节省内存或利用特定的硬件特性,会设计出不同大小的指针来指向不同的内存区域(例如,一个指向内部 RAM 的指针可能比指向外部闪存的指针更短)。

2. C 标准的灵活性: C 标准旨在具有广泛的平台兼容性。它允许实现者根据目标平台的特性来定义各种类型的大小。虽然“指向相同类型对象的指针应该具有相同的大小”是一个普遍的优化和事实,但标准并没有写死这一点。

3. 编译器优化和实现细节: 极少数情况下,编译器可能会因为某种特定的优化策略而为某些指针类型使用不同的内部表示,尽管这在现代 C 编译器中极为罕见。

如何验证指针的大小?

在 C 语言中,可以使用 `sizeof` 运算符来验证不同类型指针的大小:

```c
include

int main() {
char c;
int i;
float f;
struct MyStruct {
int x;
char y;
};
struct MyStruct ms;

char pc = &c
int pi = &i
float pf = &f
struct MyStruct pms = &ms

printf("Size of char pointer: %zu bytes ", sizeof(pc));
printf("Size of int pointer: %zu bytes ", sizeof(pi));
printf("Size of float pointer: %zu bytes ", sizeof(pf));
printf("Size of struct MyStruct pointer: %zu bytes ", sizeof(pms));

// 也可以直接使用类型名
printf("Size of (char ) pointer: %zu bytes ", sizeof(char ));
printf("Size of (int ) pointer: %zu bytes ", sizeof(int ));
printf("Size of (float ) pointer: %zu bytes ", sizeof(float ));
printf("Size of (struct MyStruct ) pointer: %zu bytes ", sizeof(struct MyStruct ));

return 0;
}
```

在大多数现代操作系统(如 Windows、Linux、macOS)和常见的硬件架构(如 x86_64, ARM64)上编译和运行这段代码,你会发现所有指针的大小都是相同的(在 64 位系统上是 8 字节,在 32 位系统上是 4 字节)。

总结:

在绝大多数现代的 C 语言编程环境中,不同类型指针的大小是相同的,因为它们都存储着一个内存地址,而系统的寻址能力决定了地址的表示大小。然而,C 语言标准并未强制要求所有指针类型大小绝对一致,并且在一些非常特殊的嵌入式系统或历史性的内存模型中,可能存在不同大小的指针。但对于绝大多数日常开发而言,可以放心地认为 `sizeof(type )` 的结果是恒定的。

网友意见

user avatar

现代 CPU 上是。

位数低的老 CPU 和嵌入式 CPU 那就不一定了,像 8086 就因为其分段的内存模型,引入了三种不同的指针:

  1. Near,大小为 16 位;
  2. Far,大小为 32 位(段 + 偏移),不进行规范化,指向的数据结构不能跨段;
  3. Huge,大小为 32 位(段 + 偏移),有规范化:这种指针的算术运算需要特殊实现来支持跨段的大型数据结构。

对于这些平台,往往一组 malloc/free 是不够的,它们的 C 库都会提供多组不同的分配/回收函数来从内存的不同区域分配内存。

如果涉及 EMS/XMS 之类需要驱动才能使用的扩展内存的话,它们分配给你搞不好就不是常规的指针了,而是一个特殊的 Handle,你需要调取驱动里面的功能来访问它们对应的内存。

对于更旧的 CPU 比如 6502,这些平台的原生程序往往都不会用 C……

类似的话题

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有