取消引用这个指针给我 -46,但我不确定为什么
Dereferencing this pointer gives me -46, but I am not sure why
这是我的程序 运行:
#include <stdio.h>
int main(void)
{
int y = 1234;
char *p = &y;
int *j = &y;
printf("%d %d\n", *p, *j);
}
我对输出有点困惑。我看到的是:
-46 1234
我写这个程序是为了实验,不确定它会输出什么。我期待 y
.
可能有一个字节
这里发生了什么"behind-the-scenes"?取消引用 p
如何给我 -46
?
正如其他人所指出的,我必须进行显式转换才能不导致 UB。我不会将该行从 char *p = &y;
更改为 char *p = (char *)&y;
,这样我就不会使下面的答案无效。
此程序不会导致任何 指出的 UB 行为。
如果你有类似的东西,
int x = 1234;
int *p = &x;
如果您取消引用指针 p
那么它将正确读取整数字节。因为您声明它是指向 int
的指针。它将知道 sizeof()
运算符要读取多少字节。通常 int
的大小是 4 bytes
(对于 32/64 位平台),但它取决于机器,这就是为什么它将使用 sizeof()
运算符来知道正确的大小并读取它的原因。
为您的代码
int y = 1234;
char *p = &y;
int *j = &y;
现在 pointer p
指向 y
但我们已将其声明为指向 char
的指针,因此它只会读取一个字节或 char 的任何字节。
1234
在二进制中将表示为
00000000 00000000 00000100 11010010
现在,如果您的机器是小端字节序,它将存储颠倒它们的字节
11010010 00000100 00000000 00000000
11010010
在address 00
Hypothetical address
,00000100
在address 01
等。
BE: 00 01 02 03
+----+----+----+----+
y: | 00 | 00 | 04 | d2 |
+----+----+----+----+
LE: 00 01 02 03
+----+----+----+----+
y: | d2 | 04 | 00 | 00 |
+----+----+----+----+
(In Hexadecimal)
所以现在如果你取消引用 pointer p
它将只读取第一个字节并且输出将是(-46
如果是 signed char
和 210
如果是 unsigned char
,根据 C 标准,普通 char 的 signed-ness 是“实现定义的。”因为字节读取将是 11010010
(因为我们指向 signed char
(在这种情况下它是 signed char
).
在您的 PC 上,负数表示为 2's Complement,因此 most-significant bit
是符号位。第一位 1
表示符号。 11010010 = –128 + 64 + 16 + 2 = –46
如果你取消引用 pointer j
它将完全读取 int
的所有字节,因为我们声明它是指向 int
的指针并且输出将是 1234
如果您将指针 j 声明为 int *j
,那么 *j
将在此处读取 sizeof(int)
4 个字节(取决于机器)。 char
或任何其他数据类型也是如此,指向它们的指针将读取大小为 的字节数,char
为 1 个字节。
正如其他人指出的那样,您需要显式转换为 char*
,因为 char *p = &y;
违反约束 - char *
和 int *
是不兼容的类型,而是写char *p = (char *)&y
.
(请注意这个答案指的是问题的原始形式,它询问程序如何知道要读取多少字节等。我在此基础上保留它,尽管地毯已从下面拉出。)
指针指的是内存中包含特定对象的位置,并且必须 incremented/decremented/indexed 具有特定的步长,反映 sizeof
指向的类型。
指针本身的可观察值(例如通过std::cout << ptr
)不需要反映任何可识别的物理地址,++ptr
也不需要将所述值递增1,sizeof(*ptr)
,或其他任何东西。指针只是对象的句柄,具有实现定义的位表示。这种表示对用户来说没有也不应该重要。用户应该使用指针的唯一目的是......好吧,指向东西。谈论它的地址是不可移植的,只在调试时有用。
无论如何,简单地说,编译器知道 read/write 有多少字节,因为指针是类型化的,并且该类型具有定义的 sizeof
、表示和到物理地址的映射。因此,基于该类型,对 ptr
的操作将被编译为适当的指令,以计算真实的硬件地址(同样,不需要对应于 ptr
的可观察值),请阅读正确的sizeof
内存数量 'bytes',add/subtract 正确的字节数,因此它指向下一个对象,等等
编写的代码存在一些问题。
首先,您通过尝试使用 %d
转换说明符打印 char
对象的数字表示来调用 未定义的行为 :
Online C 2011 draft,§7.21.6.1,第 9 条:
If a conversion specification is invalid, the behavior is undefined.282) If any argument is
not the correct type for the corresponding conversion specification, the behavior is
undefined.
是的,char
类型的对象在传递给可变参数函数时被提升为 int
; printf
是特殊的,如果您希望输出定义明确,那么参数的类型和转换说明符 必须 匹配。要使用 %d
或 unsigned char
参数以及 %u
、%o
或 %x
打印 char
的数值,您必须使用 hh
作为转换规范一部分的长度修饰符:
printf( "%hhd ", *p );
第二个问题是
行
char *p = &y;
违反了约束 - char *
和 int *
不是兼容的类型,并且可能具有不同的大小 and/or 表示 2。因此,您必须将源显式转换为目标类型:
char *p = (char *) &y;
当其中一个操作数是void *
时,此规则的一个例外;那么演员就没有必要了。
话虽如此,我采用了您的代码并添加了一个实用程序,用于转储程序中对象的地址和内容。这是 y
、p
和 j
在我的系统(SLES-10、gcc 4.1.2)上的样子:
Item Address 00 01 02 03
---- ------- -- -- -- --
y 0x7fff1a7e99cc d2 04 00 00 ....
p 0x7fff1a7e99c0 cc 99 7e 1a ..~.
0x7fff1a7e99c4 ff 7f 00 00 ....
j 0x7fff1a7e99b8 cc 99 7e 1a ..~.
0x7fff1a7e99bc ff 7f 00 00 ....
我使用的是小端 x86 系统,因此它存储多字节对象,从最低地址的最低有效字节开始:
BE: A A+1 A+2 A+3
+----+----+----+----+
y: | 00 | 00 | 04 | d2 |
+----+----+----+----+
LE: A+3 A+2 A+1 A
在小端系统上,寻址字节是最低有效字节,在本例中为 0xd2
(210
无符号,-46
有符号)。
简而言之,您正在打印该单个字节的带符号的十进制表示形式。
至于更广泛的问题,表达式 *p
的类型是char
,表达式的类型 *j
是 int
;编译器只是根据表达式的类型。编译器在将您的源代码转换为机器代码时会跟踪所有对象、表达式和类型。所以当它看到表达式 *j
时,它知道它正在处理一个整数值并适当地生成机器代码。当它看到表达式 *p
时,它知道它正在处理 char
值。
- 诚然,我所知道的几乎所有现代桌面系统都对所有指针类型使用相同的表示,但对于更古怪的嵌入式或专用平台,情况可能并非如此。
- § 6.2.5,第 28 条。
首先阅读警告说
警告:从不兼容的指针类型初始化[默认启用]
char *p = &y;
这意味着你应该显式类型转换以避免根据标准§7.21.6.1,子条款9的未定义行为(由@指出约翰博德)作为
chat *p = (char*)&y;
和
int y =1234;
此处y
是local variable
,它将存储在RAM
的stack
部分。在Linux中,机器整数存储在内存中根据 little endian
格式。假设为 y
保留的内存 4 bytes
从 0x100
到 0x104
-------------------------------------------------
| 0000 0000 | 0000 0000 | 0000 0100 | 1101 0010 |
-------------------------------------------------
0x104 0x103 0x102 0x101 0x100
y
p
j
如上所述,j
和 p
都指向相同的地址 0x100
但是当编译器将执行 *p
因为 p
是 signed character pointer
默认情况下它会检查 sign bit
并且这里 sign bit
是 1
意味着一件事是确定它要打印的输出是 负数 。
如果sign bit
是1
即负数,负数作为2的补码存储在内存中所以
actual => 1101 0010 (1st byte)
ones compliment => 0010 1101
+1
------------
0010 1110 => 46 and since sign bit was one it will print -46
打印时,如果您使用 %u
格式说明符,它用于打印 unsigned
等价物,它将 not
检查 sign bi
t,最后是其中的任何数据1 byte
得到打印。
终于
printf("%d\n",*j);
在上面的语句中,在取消引用 j
时默认为 signed pointer
并且它是一个 int
指针,因此它将检查 第 31 位 对于 sign,即 0
意味着输出将是 positive
否,即 1234.
这是我的程序 运行:
#include <stdio.h>
int main(void)
{
int y = 1234;
char *p = &y;
int *j = &y;
printf("%d %d\n", *p, *j);
}
我对输出有点困惑。我看到的是:
-46 1234
我写这个程序是为了实验,不确定它会输出什么。我期待 y
.
这里发生了什么"behind-the-scenes"?取消引用 p
如何给我 -46
?
正如其他人所指出的,我必须进行显式转换才能不导致 UB。我不会将该行从 char *p = &y;
更改为 char *p = (char *)&y;
,这样我就不会使下面的答案无效。
此程序不会导致任何
如果你有类似的东西,
int x = 1234;
int *p = &x;
如果您取消引用指针 p
那么它将正确读取整数字节。因为您声明它是指向 int
的指针。它将知道 sizeof()
运算符要读取多少字节。通常 int
的大小是 4 bytes
(对于 32/64 位平台),但它取决于机器,这就是为什么它将使用 sizeof()
运算符来知道正确的大小并读取它的原因。
为您的代码
int y = 1234;
char *p = &y;
int *j = &y;
现在 pointer p
指向 y
但我们已将其声明为指向 char
的指针,因此它只会读取一个字节或 char 的任何字节。
1234
在二进制中将表示为
00000000 00000000 00000100 11010010
现在,如果您的机器是小端字节序,它将存储颠倒它们的字节
11010010 00000100 00000000 00000000
11010010
在address 00
Hypothetical address
,00000100
在address 01
等。
BE: 00 01 02 03
+----+----+----+----+
y: | 00 | 00 | 04 | d2 |
+----+----+----+----+
LE: 00 01 02 03
+----+----+----+----+
y: | d2 | 04 | 00 | 00 |
+----+----+----+----+
(In Hexadecimal)
所以现在如果你取消引用 pointer p
它将只读取第一个字节并且输出将是(-46
如果是 signed char
和 210
如果是 unsigned char
,根据 C 标准,普通 char 的 signed-ness 是“实现定义的。”因为字节读取将是 11010010
(因为我们指向 signed char
(在这种情况下它是 signed char
).
在您的 PC 上,负数表示为 2's Complement,因此 most-significant bit
是符号位。第一位 1
表示符号。 11010010 = –128 + 64 + 16 + 2 = –46
如果你取消引用 pointer j
它将完全读取 int
的所有字节,因为我们声明它是指向 int
的指针并且输出将是 1234
如果您将指针 j 声明为 int *j
,那么 *j
将在此处读取 sizeof(int)
4 个字节(取决于机器)。 char
或任何其他数据类型也是如此,指向它们的指针将读取大小为 的字节数,char
为 1 个字节。
正如其他人指出的那样,您需要显式转换为 char*
,因为 char *p = &y;
违反约束 - char *
和 int *
是不兼容的类型,而是写char *p = (char *)&y
.
(请注意这个答案指的是问题的原始形式,它询问程序如何知道要读取多少字节等。我在此基础上保留它,尽管地毯已从下面拉出。)
指针指的是内存中包含特定对象的位置,并且必须 incremented/decremented/indexed 具有特定的步长,反映 sizeof
指向的类型。
指针本身的可观察值(例如通过std::cout << ptr
)不需要反映任何可识别的物理地址,++ptr
也不需要将所述值递增1,sizeof(*ptr)
,或其他任何东西。指针只是对象的句柄,具有实现定义的位表示。这种表示对用户来说没有也不应该重要。用户应该使用指针的唯一目的是......好吧,指向东西。谈论它的地址是不可移植的,只在调试时有用。
无论如何,简单地说,编译器知道 read/write 有多少字节,因为指针是类型化的,并且该类型具有定义的 sizeof
、表示和到物理地址的映射。因此,基于该类型,对 ptr
的操作将被编译为适当的指令,以计算真实的硬件地址(同样,不需要对应于 ptr
的可观察值),请阅读正确的sizeof
内存数量 'bytes',add/subtract 正确的字节数,因此它指向下一个对象,等等
编写的代码存在一些问题。
首先,您通过尝试使用 %d
转换说明符打印 char
对象的数字表示来调用 未定义的行为 :
Online C 2011 draft,§7.21.6.1,第 9 条:
If a conversion specification is invalid, the behavior is undefined.282) If any argument is not the correct type for the corresponding conversion specification, the behavior is undefined.
是的,char
类型的对象在传递给可变参数函数时被提升为 int
; printf
是特殊的,如果您希望输出定义明确,那么参数的类型和转换说明符 必须 匹配。要使用 %d
或 unsigned char
参数以及 %u
、%o
或 %x
打印 char
的数值,您必须使用 hh
作为转换规范一部分的长度修饰符:
printf( "%hhd ", *p );
第二个问题是
行char *p = &y;
违反了约束 - char *
和 int *
不是兼容的类型,并且可能具有不同的大小 and/or 表示 2。因此,您必须将源显式转换为目标类型:
char *p = (char *) &y;
当其中一个操作数是void *
时,此规则的一个例外;那么演员就没有必要了。
话虽如此,我采用了您的代码并添加了一个实用程序,用于转储程序中对象的地址和内容。这是 y
、p
和 j
在我的系统(SLES-10、gcc 4.1.2)上的样子:
Item Address 00 01 02 03
---- ------- -- -- -- --
y 0x7fff1a7e99cc d2 04 00 00 ....
p 0x7fff1a7e99c0 cc 99 7e 1a ..~.
0x7fff1a7e99c4 ff 7f 00 00 ....
j 0x7fff1a7e99b8 cc 99 7e 1a ..~.
0x7fff1a7e99bc ff 7f 00 00 ....
我使用的是小端 x86 系统,因此它存储多字节对象,从最低地址的最低有效字节开始:
BE: A A+1 A+2 A+3
+----+----+----+----+
y: | 00 | 00 | 04 | d2 |
+----+----+----+----+
LE: A+3 A+2 A+1 A
在小端系统上,寻址字节是最低有效字节,在本例中为 0xd2
(210
无符号,-46
有符号)。
简而言之,您正在打印该单个字节的带符号的十进制表示形式。
至于更广泛的问题,表达式 *p
的类型是char
,表达式的类型 *j
是 int
;编译器只是根据表达式的类型。编译器在将您的源代码转换为机器代码时会跟踪所有对象、表达式和类型。所以当它看到表达式 *j
时,它知道它正在处理一个整数值并适当地生成机器代码。当它看到表达式 *p
时,它知道它正在处理 char
值。
- 诚然,我所知道的几乎所有现代桌面系统都对所有指针类型使用相同的表示,但对于更古怪的嵌入式或专用平台,情况可能并非如此。
- § 6.2.5,第 28 条。
首先阅读警告说 警告:从不兼容的指针类型初始化[默认启用] char *p = &y;
这意味着你应该显式类型转换以避免根据标准§7.21.6.1,子条款9的未定义行为(由@指出约翰博德)作为
chat *p = (char*)&y;
和
int y =1234;
此处y
是local variable
,它将存储在RAM
的stack
部分。在Linux中,机器整数存储在内存中根据 little endian
格式。假设为 y
保留的内存 4 bytes
从 0x100
到 0x104
-------------------------------------------------
| 0000 0000 | 0000 0000 | 0000 0100 | 1101 0010 |
-------------------------------------------------
0x104 0x103 0x102 0x101 0x100
y
p
j
如上所述,j
和 p
都指向相同的地址 0x100
但是当编译器将执行 *p
因为 p
是 signed character pointer
默认情况下它会检查 sign bit
并且这里 sign bit
是 1
意味着一件事是确定它要打印的输出是 负数 。
如果sign bit
是1
即负数,负数作为2的补码存储在内存中所以
actual => 1101 0010 (1st byte)
ones compliment => 0010 1101
+1
------------
0010 1110 => 46 and since sign bit was one it will print -46
打印时,如果您使用 %u
格式说明符,它用于打印 unsigned
等价物,它将 not
检查 sign bi
t,最后是其中的任何数据1 byte
得到打印。
终于
printf("%d\n",*j);
在上面的语句中,在取消引用 j
时默认为 signed pointer
并且它是一个 int
指针,因此它将检查 第 31 位 对于 sign,即 0
意味着输出将是 positive
否,即 1234.