我已经在这里阅读了很多关于C指针的问题和答案,但是没有找到任何解释为什么C不允许地址以常规的形式存储的东西 - 似乎为了更简单的输入:
int a = 100;
int b = &a;
int c = &b; //etc
而不是
int a = 100;
int* b = &a;
int** c = &b; //etc
实际上,我不确定是否所有编译器都不允许这样做,或者它是否仅标记警告。但是,如果我完成同样的事情,(存储变量的地址作为参考),如果我使用指针表示法或只是直接赋值,会有什么不同?
答案 0 :(得分:5)
最根本的原因当然是当你想使用一个指针,即取消引用它来访问它指向的值时,你必须知道它的类型那个价值。
否则,编译器将如何为该特定类型生成正确的指令?
考虑指向角色的指针与指向int
的指针。在大多数情况下,后一个值是4,有时是8倍。显然这会影响访问和处理值的方式。
不区分指针类型,这就变得不可能了。
答案 1 :(得分:4)
理论上,指针只是整数,所以你可以在很多情况下对它们进行处理。汇编语言没有指针类型,而是使用“直接”或“间接”访问。
但是,有许多CPU,其中地址总线和数据总线具有不同的大小。例如,在平均8位微控制器上,通常可以寻址65k的内存(16位),但只能执行8位数据的计算。此外,C允许使用模糊的架构 - 理论上,指针不需要直接对应物理地址。
这就是发明指针的主要原因。但是当然,在C语言中有各种各样的东西使指针不仅仅是一个原始数字。你可以做指针运算,你可以让数组衰减成指针,你可以通过指针得到一些类型安全性和const正确性,等等。
另外,请注意int
是签名类型,对于系统具有负地址,它很可能没有任何意义。
这是C标准6.3.2.3关于转换的说法:
整数可以转换为任何指针类型。除了以前 指定,结果是实现定义的,可能不是 正确对齐,可能不指向引用的实体 类型,可能是陷阱表示。
任何指针类型都可以转换为整数类型。除了 之前指定的,结果是实现定义的。如果 结果不能用整数类型表示,行为是 未定义。结果不必在任何值的范围内 整数类型。
换句话说,指针和整数之间的转换是特定于系统的。如果你做了一些没有意义的事情(例如将指针值存储在签名的int
中,就像在你的例子中那样),那么你就是你自己而且编译器和语言都没有做出任何保证。会发生什么。
答案 2 :(得分:2)
因为如果所有地址仅存储在公共数据类型中,例如int
或void *
,那么您拥有的唯一信息就是地址,这还不够使用该地址的数据。特别是,为了使用该地址的数据,您还需要知道:
该地址存储了多少数据;以及
该数据旨在表示什么。
如您所知,C中的不同数据类型存储在不同的内存量中。 char
总是占用一个字节,而short
占用两个字节的典型情况是典型的,例如int
占用四个字节。
变量的地址始终是存储在该变量中的数据的第一个字节的地址。例如,如果您要定义int var = 0xDEADBEEF;
并且编译器决定将该变量存储在内存位置100,则内存将如下所示(在具有四个字节int
s的小端系统上):
-----------------------------------------
| 100 | 101 | 102 | 103 |
| 0xEF | 0xBE | 0xAD | 0xDE |
-----------------------------------------
另一方面,如果您要定义char c = 0x09;
并且编译器将其存储在内存位置200,那么它可能如下所示:
-----------------------------------------
| 200 | 201 | 202 | 203 |
| 0x09 | Garbage | Garbage | Garbage |
-----------------------------------------
所以,如果这是允许的话:
int main(void) {
int var = 0xDEADBEEF;
char c = 0x09;
int pv = &var; /* Not allowed in ISO C */
int pc = &c; /* Not allowed in ISO C */
int sum = my_func(pv, pc);
....
}
int my_func(int p1, int p2) {
int a = *p1; /* Not allowed in ISO C */
int b = *p2; /* Not allowed in ISO C */
return a + b;
}
那么编译器如何知道要分配给a
中的b
和my_func()
的值?当然,编译器知道p1
指向的数据的地址,但是当它想要取消引用该指针时,找出数据的值,并将值存储在a
中,是否需要内存位置100
的值?或内存位置100
和101
的值?或内存位置100
,101
,102
和103
的值?或其他什么?
如果您只是将所有指针存储在一种类型的变量中,编译器会知道每个数据元素在内存中的开始,但它不知道多少数据在那个地址。
如果,另一方面,您需要人们在int
中存储指向int *
的指针,并在{{1}中指向char
的指针然后编译器 知道。如果你有一个包含地址char *
的{{1}}指针,并且你要求编译器遵从它,那么它就知道你想要存储在地址int *
和接下来的三个地址的值bytes ,因为它是100
告诉编译器在那里存储了100
,而int *
占用了4个字节(在这个特定的假设实现上) )。
另一方面,如果您将地址int
存储在int
中并要求编译器获取地址100
的值,则它知道仅获取存储在该单个存储器地址的一个字节值,并忽略任何后续字节,因为它知道char *
占用一个字节。
因此,当您拥有不同大小的数据类型时,知道他们的地址是不够的。您还需要知道它们占用了多少字节的内存,否则当您尝试检索该值时,您不知道要读取多少字节的内存。如果使用不同的指针类型指向每种基本类型,那么执行知道要读取多少字节的内存。
在使用ASCII的小端架构上考虑这个(非可移植)程序:
100
输出:
char
事实证明,有时字符串和#include <stdio.h>
void print_string(void * ptr) {
char * c = ptr;
printf("String is: %s\n", c);
}
void print_int(void * ptr) {
int * p = ptr;
printf("Int is: %d\n", *p);
}
int main(void) {
char * c = "Ptr";
int n = 7500880;
print_string(&n);
print_string(c);
print_int(&n);
print_int(c);
return 0;
}
s在某种程度上是相同的。这是怎么回事?
在这种情况下,三个字符的字符串paul@local:~/src/c/scratch$ ./testmem
String is: Ptr
String is: Ptr
Int is: 7500880
Int is: 7500880
paul@local:~/src/c/scratch$
(四个字符,包括终止空值)将存储为ASCII字符int
,即"Ptr"
,然后是ASCII字符'P'
,即0x50
,后跟ASCII字符't'
,后跟0x74
,后跟空字符'r'
。如果字符串从内存位置100开始,那么四个字节将如下所示:
0x72
数字0x00
,即-----------------------------------------
| 100 | 101 | 102 | 103 |
| 0x50 | 0x74 | 0x72 | 0x00 |
-----------------------------------------
,表示为十六进制,将在little-endian机器上以相反顺序存储字节,并存储四字节整数7500880
也由四个字节表示:
0x00727450
因此,在这台特定的机器上,内存中的这四个字节可以 解释为整数7500880
或作为字符串-----------------------------------------
| 200 | 201 | 202 | 203 |
| 0x50 | 0x74 | 0x72 | 0x00 |
-----------------------------------------
- 表示两条数据的位模式是相同的。换句话说,如果我在没有任何背景的情况下向你展示了这块内存:
7500880
并问你,&#34;我是否将字符串"Ptr"
存储在内存位置-----------------------------------------
| 300 | 301 | 302 | 303 |
| 0x50 | 0x74 | 0x72 | 0x00 |
-----------------------------------------
,或者是否将值"Ptr"
的{{1}}存储在内存中地点300
?&#34;,您无法告诉我。就此而言,我本可以在该位置存储一个四字节RGBA值,红色值为80,绿色值为116,蓝色值为114,alpha值为0,您就可以了没有更聪明的人。
这实际上是对一般事实的具体观察,我们存储在计算机上的任何信息必须表示为位和字节,并且位和字节本身不具有任何意义。对于任何编码都是如此。例如,如果我们将字符串int
存储为UTF-16而不是ASCII,那么它将表示为750880
,而不是表示为300
,但意思是相同。
因此,如果我们将信息表示为位,并且我们希望在任何实际的通信发生任何战斗机会,那么我们需要确保接收方知道如何以与我们编码方式一致的方法解码位。我可以给你发信息&#34; SOS&#34;在使用"Ptr"
的摩尔斯电码中,如果你有一个破碎的莫尔斯代码表告诉你0x50747200
实际上代表&#34; H&#34;和0x5000740072000000
代表&#34 ; A&#34;,然后消息将永远丢失。
所有这一切的结果是,即使您知道存储在特定地址的数据的大小,这仍然还不够 - 您还必须知道如何表示数据。
例如,在我的系统上,... --- ...
和...
都占用8个字节,值---
可以在两种类型中精确表示,但long
的位模式{1}}将是:
double
,0xDEADBEEF
的位模式为:
long
相同的值,位级别的完全不同的表示。所以,即使我知道变量的地址,并且我知道它占用了8个字节,我仍然需要知道更多信息 - 我需要知道类型数据>因为如果不知道如何表示数据,我就无法理解数据,0xEFBEADDE00000000
和double
不能以相同的方式表示相同的数据。< / p>
所以,回到简短的回答,如果您只是使用0x41EBD5B7DDE00000
存储任何和所有指针,或者即使您刚使用了long
,也无法做出任何指示您在这些地址找到的数据的感觉,因为您需要知道有多少数据,以及如何解释您在那里找到的位。指向double
的指针存储在int
中,指向void *
的指针存储在int
中,指针指向int *
的指针1}}存储在double
中,然后您 知道这些事情,因此您可以理解您的数据。
答案 3 :(得分:0)
什么是指针?
指针是一个变量,用于存储另一个变量的内存位置。
如何声明指针?
int *a;
int* a;
int*a; // all gives the same meaning
为什么我们需要指针?
指针是一种有效的编程方式,因为程序员能够访问存储变量的存储位置(使用交换功能进行最佳解释)。
<强>因此!!!! 强>
简单地做
int b = &a;
不暗示 b 是一个指针,它只是一个存储另一个变量的地址的变量, a 的值可以不能使用 b 访问。
,而
int* b = &a;
暗示 b 是指针并保存变量 a 的内存位置,因此取消引用指针将给出变量 a
答案 4 :(得分:-2)
很久以前C使用int和int * interchangable
但是按时K&amp; R他们会尝试更多的类型安全性。
你可以看到狮子会评论在何处使用
int - &gt; somefild
和struct {int a}
所以你可以对任何指针指针进行类型转换 - &gt; a和u得到*指针值的int解释
并且那个时候你不需要联合和(类型)cose struct得到它