未分配的指针是否有虚假地址?
Does unasigned pointers have bogus address?
所以我正在做 CS50 第 4 课记忆。
David 说我们必须在声明 int *x 时分配一个地址;作为指针以便在其中存储值(Ex 45、23 等)。他还说,如果你不初始化一个指针,然后尝试通过取消引用它来将一个值放入其中,如下面的 y 所示,你是在要求计算机将值存储在虚假地址或随机地址中?
int main(void)
{
int *x;
int *y;
x = malloc(sizeof(int));
*x = 42;
*y = 13;
}
但什么是虚假地址?当我声明 int *x;里面已经有地址了吗?这怎么可能?我知道指针 x 的值将被存储的内存位置可能有一些 prev 操作的残余,但我不明白那里怎么会有地址。
这里的int *x是一个野指针,这意味着它可能被初始化为一个可能不是有效地址的非NULL垃圾值。
I understand that the memory location where pointer x's value will be stored might have some remnants of prev operations but i don't understand how there can be an address there.
这就是为什么您会看到“虚假地址”或“随机地址”之类的术语。垃圾是一个虚假地址。垃圾,如果理解为地址,就是随机地址。
那里只能有一个 有效 地址(好坏是另一个问题)。但是如果有随机垃圾,你把它当成一个地址,它很可能是一个“假地址”或者“随机地址”。
这两个变量自动存储时长
int *x;
int *y;
当然有他们的地址。您可以通过以下方式输出他们的地址
printf( "&x = %p\n", ( void * )&x );
printf( "&y = %p\n", ( void * )&y );
但是变量 x 和 y 本身没有初始化并且具有不确定的值。所以在这个声明中取消引用这些指针
*y = 13;
导致未定义的行为。
如果你想取消引用一个指针,它必须指向一个对象,就像在这些语句中所做的那样
x = malloc(sizeof(int));
*x = 42;
在上面的第一个语句之后,指针 x 指向为 int
类型的对象分配的内存。所以取消引用指针
*x = 42;
您可以更改对象。
I understand that the memory location where pointer x's value will be stored might have some remnants of prev operations but i don't understand how there can be an address there.
假设前面的操作将该内存用作 uint32_t
对象(无符号 32 位整数),并假设 C 实现中的 int *
也是 32 位。例如,假设您系统上的某个地址是 0x103F0。当内存用于 uint32_t
时,它可能已用于存储无符号整数值 66544。66544 的十六进制为 0x103F0。所以内存将包含0x103F0,与假设地址相同。
每个有效地址都是一些特定的位设置1。每个位的设置都是一些无符号整数。因此,x
的未初始化内存中很容易有一些位表示一个地址。其他类型也可能发生这种情况。 x
的内存可能已被用作 char
的数组或 float
,用于这些的位也可能与用于表示 0x103F0 的位相同。
另一个问题是,当您定义 int *x;
然后使用 x
时,现代编译器不会只是机械地为 x
保留一些内存,然后从中加载该值的内容记忆。他们会尝试优化您的程序(除非优化已关闭)。在这样做时,他们试图寻找实现源代码定义行为的“最佳”程序。但是,当您使用未初始化的变量时,该变量的值不是由 C 标准定义的。根据具体情况,标准可能根本没有定义使用它的行为。那么你的程序没有定义的行为,而实现源代码那部分定义行为的“最佳”指令集根本就没有指令——编译器可能只是删除你程序的那部分,或者可能只是用程序的其他部分或表现出令新程序员感到惊讶的其他行为。
脚注
1 有时可以有多个位设置表示相同的地址,例如当某些位未使用或内存是重叠的基址和偏移方案中的地址时段。
首先,请记住 x
和 y
是独立于它们指向的内容而存在的变量。 x
和 y
的初始值是 indeterminate - 它们可能是 0x00000000
,它们可能是 0xdeadbeef
,它们可能是根本不对应于有效地址值的位模式。
x
和 y
变量的 space 必须取自 某处 ,并且由于内存不是无限的,内存位置得到重用;一些内存位置被重用很多。在大多数实现中,内存不会自动被擦除1,因此当您创建一个新对象时,它将包含最后写入这些对象的位模式bytes2.
C 有一个对象的 lifetime 的概念,这是保证为该对象保留存储空间的程序执行周期。如果指针在对象的生命周期内存储对象的地址,则它是 有效的 。通过以下两种方式之一获取有效指针值:
- 在对象的生命周期内对对象使用
&
运算符
- 调用
malloc
、calloc
或 realloc
,为对象动态分配 space,就像您为 x
[=141= 所做的那样]3.
例如:
void foo( void )
{
int *ptr; // ptr is initially indeterminate and invalid
for ( int i = 0; i < 10; i++ )
{
ptr = &i; // i's lifetime is each iteration of the for loop;
printf( "%d = %d\n", *ptr, i ); // ptr is valid within the loop;
}
// ptr still stores the address of i, but i's lifetime has ended,
// so ptr is *no longer valid* - attempting to read or write it now
// will lead to undefined behavior
}
在 i
的生命周期结束后,为它保留的 space 可以被其他东西使用。如果我们在循环结束后尝试通过 ptr
读取或写入它,结果可能不是我们期望的。这样做的行为是 undefined,这意味着编译器和 运行time 环境不需要以任何特定方式处理这种情况。它可能会像我们预期的那样工作,我们可能会在某处损坏数据,我们可能会导致 运行 时间错误,或者其他任何事情都可能发生。
同样,执行
*y = 13;
在您的程序中会有未定义的行为,因为 y
不会在对象的生命周期内在您的程序中存储该对象的地址。从字面上看,此时任何事情都可能发生——您的程序可能会按预期运行,您可能会损坏程序中其他地方的数据,您可能会导致您的程序分支到一个随机函数,您可能会导致 运行 时间错误,或者字面上任何其他事情都可能发生。每次 运行 结果可能会有所不同。
编辑
解决评论中的问题:
Are you referring to pointers here? Can pointers be considered as object? or is it just the ints and chars that are to be called as object?
是的,指针变量 x
和 y
是对象(在 C 意义上,它们是可以存储值的内存区域)。为了更好地说明这一点,我写了以下内容:
#include <stdio.h>
#include <stdlib.h>
#include "dumper.h"
int main( void )
{
int *x;
int *y;
int a;
char *names[] = { "a", "x", "y", "*x", "*y" };
void *addrs[] = { &a, &x, &y, NULL, NULL };
size_t sizes[] = { sizeof a, sizeof x, sizeof y, sizeof *x, sizeof *y };
puts( "Initial states of a, x, and y:" );
dumper( names, addrs, sizes, 3, stdout );
x = calloc( 1, sizeof *x ); // makes sure *x is initialized to 0
if ( x )
{
addrs[3] = x;
puts( "States of a, x, and y after allocating memory for x" );
dumper( names, addrs, sizes, 4, stdout );
*x = 0x11223344;
puts( "States of a, x, y, and *x after assigning *x" );
dumper( names, addrs, sizes, 4, stdout );
}
y = &a;
addrs[4] = y;
puts( "States of a, x, y, *x, and *y after assigning &a to y" );
dumper( names, addrs, sizes, 5, stdout );
*y = 0x55667788;
puts( "States of a, x, y, *x, and *y after assigning to *y" );
dumper( names, addrs, sizes, 5, stdout );
free( x );
return 0;
}
dumper
是我编写的一个小实用程序,用于将对象的地址和内容转储到指定的输出流。
构建和 运行 代码后,我得到了变量初始状态的输出:
Initial states of a, x, and y:
Item Address 00 01 02 03
---- ------- -- -- -- --
a 0x7ffee3bc59f4 2c b3 0c 1b ,...
x 0x7ffee3bc5a00 01 00 00 00 ....
0x7ffee3bc5a04 00 00 00 00 ....
y 0x7ffee3bc59f8 80 5b bc e3 .[..
0x7ffee3bc59fc fe 7f 00 00 ....
变量 a
位于地址 0x7ffee3bc59f4
并占用 4 个字节 - 这个 运行 的初始内容是 0x1b0cb32c
(x86 是小端,所以字节从最低有效位到最高有效位排序)。由于 a
没有明确初始化,它的初始内容是不确定的 - 每次我 运行 这个程序 a
的初始值可能会不同(它的地址也会 - 作为防御针对恶意软件,大多数操作系统从 运行 运行).
中随机选择位置
变量 x
从地址 0x7ffee3bc5a04
开始,占用 8 个字节(x86 上的堆栈“向下”增长,因此我们从较高的地址开始)。类似地,变量 y
位于地址 0x7ffee3bc59fc
并且也占用 8 个字节。和a
一样,x
和y
的初始内容是不确定的,会从运行运行.
变化
为 x
将指向的 int
对象分配 space 后,我有这个:
States of a, x, and y after allocating memory for x
Item Address 00 01 02 03
---- ------- -- -- -- --
a 0x7ffee3bc59f4 2c b3 0c 1b ,...
x 0x7ffee3bc5a00 a0 25 50 1e .%P.
0x7ffee3bc5a04 c2 7f 00 00 ....
y 0x7ffee3bc59f8 80 5b bc e3 .[..
0x7ffee3bc59fc fe 7f 00 00 ....
*x 0x7fc21e5025a0 00 00 00 00 ....
变量 x
现在存储值 0x7fc21e5025a0
,这是一个足够大的内存块地址,可以存储 int
价值。由于我使用 calloc
分配内存,因此它的初始内容是所有位 0。我现在可以通过 表达式 *x
为该对象分配一个新的 int
值,这给了我:
States of a, x, y, and *x after assigning *x
Item Address 00 01 02 03
---- ------- -- -- -- --
a 0x7ffee3bc59f4 2c b3 0c 1b ,...
x 0x7ffee3bc5a00 a0 25 50 1e .%P.
0x7ffee3bc5a04 c2 7f 00 00 ....
y 0x7ffee3bc59f8 80 5b bc e3 .[..
0x7ffee3bc59fc fe 7f 00 00 ....
*x 0x7fc21e5025a0 44 33 22 11 D3".
所以我更新了 x
指向 的 int
对象(即存储地址)。
最后,我将 y
设置为指向 a
,得到:
States of a, x, y, *x, and *y after assigning &a to y
Item Address 00 01 02 03
---- ------- -- -- -- --
a 0x7ffee3bc59f4 2c b3 0c 1b ,...
x 0x7ffee3bc5a00 a0 25 50 1e .%P.
0x7ffee3bc5a04 c2 7f 00 00 ....
y 0x7ffee3bc59f8 f4 59 bc e3 .Y..
0x7ffee3bc59fc fe 7f 00 00 ....
*x 0x7fc21e5025a0 44 33 22 11 D3".
*y 0x7ffee3bc59f4 2c b3 0c 1b ,...
变量y
中存储的值是变量a
的地址:0x7ffee3bc59f4
。如您所见,表达式 *y
与变量 a
保持相同的值。我现在可以通过写入 *y
来更改 a
的值,这让我们得到:
States of a, x, y, *x, and *y after assigning to *y
Item Address 00 01 02 03
---- ------- -- -- -- --
a 0x7ffee3bc59f4 88 77 66 55 .wfU
x 0x7ffee3bc5a00 a0 25 50 1e .%P.
0x7ffee3bc5a04 c2 7f 00 00 ....
y 0x7ffee3bc59f8 f4 59 bc e3 .Y..
0x7ffee3bc59fc fe 7f 00 00 ....
*x 0x7fc21e5025a0 44 33 22 11 D3".
*y 0x7ffee3bc59f4 88 77 66 55 .wfU
指针变量没有什么神奇之处——它们只是存储特定类型值(地址)的内存块。不同的指针类型 可能 具有不同的大小 and/or 表示(即,int *
变量可能看起来不同于 char *
变量,后者看起来可能不同于struct foo *
变量)。唯一的规则是
char *
和 void *
具有相同的大小和对齐方式;
- 指向限定类型的指针与指向其非限定等价物的指针具有相同的大小和对齐方式(即,
const int *
和 int *
应具有相同的大小和对齐方式);
- 所有
struct
指针类型具有相同的大小和对齐方式(例如,struct foo *
和 struct bar *
看起来相同);
- 所有
union
指针类型具有相同的大小和对齐方式;
对指针值的操作 很特殊,它们的语法可能令人困惑。但是指针只是另一种数据类型,指针变量只是另一种对象。
- 也就是说,设置为所有位 0 或一些其他明确定义的 "not a value" 位模式。
- 我们不打算在这里讨论虚拟内存和物理内存之间的区别。
- 您没有为
x
本身分配 space - 您正在为 x
将 [=126] 的 int
对象分配 space =]指向。
所以我正在做 CS50 第 4 课记忆。
David 说我们必须在声明 int *x 时分配一个地址;作为指针以便在其中存储值(Ex 45、23 等)。他还说,如果你不初始化一个指针,然后尝试通过取消引用它来将一个值放入其中,如下面的 y 所示,你是在要求计算机将值存储在虚假地址或随机地址中?
int main(void)
{
int *x;
int *y;
x = malloc(sizeof(int));
*x = 42;
*y = 13;
}
但什么是虚假地址?当我声明 int *x;里面已经有地址了吗?这怎么可能?我知道指针 x 的值将被存储的内存位置可能有一些 prev 操作的残余,但我不明白那里怎么会有地址。
这里的int *x是一个野指针,这意味着它可能被初始化为一个可能不是有效地址的非NULL垃圾值。
I understand that the memory location where pointer x's value will be stored might have some remnants of prev operations but i don't understand how there can be an address there.
这就是为什么您会看到“虚假地址”或“随机地址”之类的术语。垃圾是一个虚假地址。垃圾,如果理解为地址,就是随机地址。
那里只能有一个 有效 地址(好坏是另一个问题)。但是如果有随机垃圾,你把它当成一个地址,它很可能是一个“假地址”或者“随机地址”。
这两个变量自动存储时长
int *x;
int *y;
当然有他们的地址。您可以通过以下方式输出他们的地址
printf( "&x = %p\n", ( void * )&x );
printf( "&y = %p\n", ( void * )&y );
但是变量 x 和 y 本身没有初始化并且具有不确定的值。所以在这个声明中取消引用这些指针
*y = 13;
导致未定义的行为。
如果你想取消引用一个指针,它必须指向一个对象,就像在这些语句中所做的那样
x = malloc(sizeof(int));
*x = 42;
在上面的第一个语句之后,指针 x 指向为 int
类型的对象分配的内存。所以取消引用指针
*x = 42;
您可以更改对象。
I understand that the memory location where pointer x's value will be stored might have some remnants of prev operations but i don't understand how there can be an address there.
假设前面的操作将该内存用作 uint32_t
对象(无符号 32 位整数),并假设 C 实现中的 int *
也是 32 位。例如,假设您系统上的某个地址是 0x103F0。当内存用于 uint32_t
时,它可能已用于存储无符号整数值 66544。66544 的十六进制为 0x103F0。所以内存将包含0x103F0,与假设地址相同。
每个有效地址都是一些特定的位设置1。每个位的设置都是一些无符号整数。因此,x
的未初始化内存中很容易有一些位表示一个地址。其他类型也可能发生这种情况。 x
的内存可能已被用作 char
的数组或 float
,用于这些的位也可能与用于表示 0x103F0 的位相同。
另一个问题是,当您定义 int *x;
然后使用 x
时,现代编译器不会只是机械地为 x
保留一些内存,然后从中加载该值的内容记忆。他们会尝试优化您的程序(除非优化已关闭)。在这样做时,他们试图寻找实现源代码定义行为的“最佳”程序。但是,当您使用未初始化的变量时,该变量的值不是由 C 标准定义的。根据具体情况,标准可能根本没有定义使用它的行为。那么你的程序没有定义的行为,而实现源代码那部分定义行为的“最佳”指令集根本就没有指令——编译器可能只是删除你程序的那部分,或者可能只是用程序的其他部分或表现出令新程序员感到惊讶的其他行为。
脚注
1 有时可以有多个位设置表示相同的地址,例如当某些位未使用或内存是重叠的基址和偏移方案中的地址时段。
首先,请记住 x
和 y
是独立于它们指向的内容而存在的变量。 x
和 y
的初始值是 indeterminate - 它们可能是 0x00000000
,它们可能是 0xdeadbeef
,它们可能是根本不对应于有效地址值的位模式。
x
和 y
变量的 space 必须取自 某处 ,并且由于内存不是无限的,内存位置得到重用;一些内存位置被重用很多。在大多数实现中,内存不会自动被擦除1,因此当您创建一个新对象时,它将包含最后写入这些对象的位模式bytes2.
C 有一个对象的 lifetime 的概念,这是保证为该对象保留存储空间的程序执行周期。如果指针在对象的生命周期内存储对象的地址,则它是 有效的 。通过以下两种方式之一获取有效指针值:
- 在对象的生命周期内对对象使用
&
运算符 - 调用
malloc
、calloc
或realloc
,为对象动态分配 space,就像您为x
[=141= 所做的那样]3.
例如:
void foo( void )
{
int *ptr; // ptr is initially indeterminate and invalid
for ( int i = 0; i < 10; i++ )
{
ptr = &i; // i's lifetime is each iteration of the for loop;
printf( "%d = %d\n", *ptr, i ); // ptr is valid within the loop;
}
// ptr still stores the address of i, but i's lifetime has ended,
// so ptr is *no longer valid* - attempting to read or write it now
// will lead to undefined behavior
}
在 i
的生命周期结束后,为它保留的 space 可以被其他东西使用。如果我们在循环结束后尝试通过 ptr
读取或写入它,结果可能不是我们期望的。这样做的行为是 undefined,这意味着编译器和 运行time 环境不需要以任何特定方式处理这种情况。它可能会像我们预期的那样工作,我们可能会在某处损坏数据,我们可能会导致 运行 时间错误,或者其他任何事情都可能发生。
同样,执行
*y = 13;
在您的程序中会有未定义的行为,因为 y
不会在对象的生命周期内在您的程序中存储该对象的地址。从字面上看,此时任何事情都可能发生——您的程序可能会按预期运行,您可能会损坏程序中其他地方的数据,您可能会导致您的程序分支到一个随机函数,您可能会导致 运行 时间错误,或者字面上任何其他事情都可能发生。每次 运行 结果可能会有所不同。
编辑
解决评论中的问题:
Are you referring to pointers here? Can pointers be considered as object? or is it just the ints and chars that are to be called as object?
是的,指针变量 x
和 y
是对象(在 C 意义上,它们是可以存储值的内存区域)。为了更好地说明这一点,我写了以下内容:
#include <stdio.h>
#include <stdlib.h>
#include "dumper.h"
int main( void )
{
int *x;
int *y;
int a;
char *names[] = { "a", "x", "y", "*x", "*y" };
void *addrs[] = { &a, &x, &y, NULL, NULL };
size_t sizes[] = { sizeof a, sizeof x, sizeof y, sizeof *x, sizeof *y };
puts( "Initial states of a, x, and y:" );
dumper( names, addrs, sizes, 3, stdout );
x = calloc( 1, sizeof *x ); // makes sure *x is initialized to 0
if ( x )
{
addrs[3] = x;
puts( "States of a, x, and y after allocating memory for x" );
dumper( names, addrs, sizes, 4, stdout );
*x = 0x11223344;
puts( "States of a, x, y, and *x after assigning *x" );
dumper( names, addrs, sizes, 4, stdout );
}
y = &a;
addrs[4] = y;
puts( "States of a, x, y, *x, and *y after assigning &a to y" );
dumper( names, addrs, sizes, 5, stdout );
*y = 0x55667788;
puts( "States of a, x, y, *x, and *y after assigning to *y" );
dumper( names, addrs, sizes, 5, stdout );
free( x );
return 0;
}
dumper
是我编写的一个小实用程序,用于将对象的地址和内容转储到指定的输出流。
构建和 运行 代码后,我得到了变量初始状态的输出:
Initial states of a, x, and y:
Item Address 00 01 02 03
---- ------- -- -- -- --
a 0x7ffee3bc59f4 2c b3 0c 1b ,...
x 0x7ffee3bc5a00 01 00 00 00 ....
0x7ffee3bc5a04 00 00 00 00 ....
y 0x7ffee3bc59f8 80 5b bc e3 .[..
0x7ffee3bc59fc fe 7f 00 00 ....
变量 a
位于地址 0x7ffee3bc59f4
并占用 4 个字节 - 这个 运行 的初始内容是 0x1b0cb32c
(x86 是小端,所以字节从最低有效位到最高有效位排序)。由于 a
没有明确初始化,它的初始内容是不确定的 - 每次我 运行 这个程序 a
的初始值可能会不同(它的地址也会 - 作为防御针对恶意软件,大多数操作系统从 运行 运行).
变量 x
从地址 0x7ffee3bc5a04
开始,占用 8 个字节(x86 上的堆栈“向下”增长,因此我们从较高的地址开始)。类似地,变量 y
位于地址 0x7ffee3bc59fc
并且也占用 8 个字节。和a
一样,x
和y
的初始内容是不确定的,会从运行运行.
为 x
将指向的 int
对象分配 space 后,我有这个:
States of a, x, and y after allocating memory for x
Item Address 00 01 02 03
---- ------- -- -- -- --
a 0x7ffee3bc59f4 2c b3 0c 1b ,...
x 0x7ffee3bc5a00 a0 25 50 1e .%P.
0x7ffee3bc5a04 c2 7f 00 00 ....
y 0x7ffee3bc59f8 80 5b bc e3 .[..
0x7ffee3bc59fc fe 7f 00 00 ....
*x 0x7fc21e5025a0 00 00 00 00 ....
变量 x
现在存储值 0x7fc21e5025a0
,这是一个足够大的内存块地址,可以存储 int
价值。由于我使用 calloc
分配内存,因此它的初始内容是所有位 0。我现在可以通过 表达式 *x
为该对象分配一个新的 int
值,这给了我:
States of a, x, y, and *x after assigning *x
Item Address 00 01 02 03
---- ------- -- -- -- --
a 0x7ffee3bc59f4 2c b3 0c 1b ,...
x 0x7ffee3bc5a00 a0 25 50 1e .%P.
0x7ffee3bc5a04 c2 7f 00 00 ....
y 0x7ffee3bc59f8 80 5b bc e3 .[..
0x7ffee3bc59fc fe 7f 00 00 ....
*x 0x7fc21e5025a0 44 33 22 11 D3".
所以我更新了 x
指向 的 int
对象(即存储地址)。
最后,我将 y
设置为指向 a
,得到:
States of a, x, y, *x, and *y after assigning &a to y
Item Address 00 01 02 03
---- ------- -- -- -- --
a 0x7ffee3bc59f4 2c b3 0c 1b ,...
x 0x7ffee3bc5a00 a0 25 50 1e .%P.
0x7ffee3bc5a04 c2 7f 00 00 ....
y 0x7ffee3bc59f8 f4 59 bc e3 .Y..
0x7ffee3bc59fc fe 7f 00 00 ....
*x 0x7fc21e5025a0 44 33 22 11 D3".
*y 0x7ffee3bc59f4 2c b3 0c 1b ,...
变量y
中存储的值是变量a
的地址:0x7ffee3bc59f4
。如您所见,表达式 *y
与变量 a
保持相同的值。我现在可以通过写入 *y
来更改 a
的值,这让我们得到:
States of a, x, y, *x, and *y after assigning to *y
Item Address 00 01 02 03
---- ------- -- -- -- --
a 0x7ffee3bc59f4 88 77 66 55 .wfU
x 0x7ffee3bc5a00 a0 25 50 1e .%P.
0x7ffee3bc5a04 c2 7f 00 00 ....
y 0x7ffee3bc59f8 f4 59 bc e3 .Y..
0x7ffee3bc59fc fe 7f 00 00 ....
*x 0x7fc21e5025a0 44 33 22 11 D3".
*y 0x7ffee3bc59f4 88 77 66 55 .wfU
指针变量没有什么神奇之处——它们只是存储特定类型值(地址)的内存块。不同的指针类型 可能 具有不同的大小 and/or 表示(即,int *
变量可能看起来不同于 char *
变量,后者看起来可能不同于struct foo *
变量)。唯一的规则是
char *
和void *
具有相同的大小和对齐方式;- 指向限定类型的指针与指向其非限定等价物的指针具有相同的大小和对齐方式(即,
const int *
和int *
应具有相同的大小和对齐方式); - 所有
struct
指针类型具有相同的大小和对齐方式(例如,struct foo *
和struct bar *
看起来相同); - 所有
union
指针类型具有相同的大小和对齐方式;
对指针值的操作 很特殊,它们的语法可能令人困惑。但是指针只是另一种数据类型,指针变量只是另一种对象。
- 也就是说,设置为所有位 0 或一些其他明确定义的 "not a value" 位模式。
- 我们不打算在这里讨论虚拟内存和物理内存之间的区别。
- 您没有为
x
本身分配 space - 您正在为x
将 [=126] 的int
对象分配 space =]指向。