指向指针的未初始化指针的临时值
Casual value of a not initialized pointer to pointer
当我定义一个指针而不初始化它时:
int *pi;
它指向内存的随机部分。
当我定义一个指向指针的指针而不初始化它时会发生什么?
int **ppi;
它指向哪里?它应该指向另一个指针,但我没有定义它所以它可能指向内存的随机部分?如果可以的话,能不能举例说明一下区别?
为了清楚起见,请考虑以下声明
T *ptr;
其中 T
是某种类型说明符。如果声明的变量 ptr
具有自动存储持续时间,则指针既不显式也不隐式地初始化。
因此指针具有不确定的值。
T
可以是任何类型。您可以将 T 定义为例如
typedef int T;
或
typedef int *T;
指针是标量对象。因此在这个声明中
typedef int *T;
T *ptr;
指针 ptr
具有不确定的值,与声明中的方式相同
typedef int T;
T *ptr;
任何未初始化的局部变量都包含不确定的值。不分类型。例如,未初始化的 int
、int*
或 int**
.
之间没有明显区别
但是,C 中有一条规则说,如果您不访问此类未初始化局部变量的地址,而是使用它的值,则会调用未定义的行为——这意味着错误,可能是崩溃等。理由很可能是这些变量可能在寄存器中分配并且没有可寻址的内存位置。有关详细信息,请参阅 。
所以下面这些例子都是错误的,因为局部变量本身的地址从未被使用过:
{
int i;
int* ip;
int** ipp;
printf("%d\n, i); // undefined behavior
printf("%p\n, (void*)ip); // undefined behavior
printf("%p\n, (void*)ipp); // undefined behavior
}
但是,如果你取某处变量的地址,C 就不那么严格了。在这种情况下,您最终会得到一个 不确定值 的变量,这意味着它可能包含任何内容,并且如果您多次访问它,该值可能会不一致。如果是指针,这可能是一个“随机地址”,但不一定如此。
不确定的值可能是所谓的“陷阱表示”,即该类型的禁止二进制序列。在这种情况下,访问变量(读取或写入)会调用未定义的行为。这不太可能发生在普通的 int
上,除非你有一个非常奇特的系统,它不使用 2 的补码——因为在标准的 2 的补码系统中,int
的所有值组合都是有效的,并且有无填充位、负零等
示例(假设为 2 的补码):
{
int i;
int* ip = &i;
printf("%d\n", *ip); // unspecified behavior, might print anything
}
未指定的行为 意味着编译器不需要记录行为。您可以获得任何类型的输出,并且不需要保持一致。但至少程序不会像在 未定义行为.
的情况下那样崩溃和烧毁
但是陷阱表示更可能是指针变量的事情。特定的 CPU 可以有一个受限地址 space 或者在低级初始化时,MMU 可以设置为将某些区域设置为虚拟区域,某些区域仅包含数据或某些区域仅包含代码等. 即使您将无效地址值读入变址寄存器,这样的 CPU 也可能会产生硬件异常。如果您尝试通过无效地址访问内存,它肯定很可能会这样做。
例如,MMU 可能会阻止试图从内存的数据段执行代码的失控代码,或者将代码内存的内容当作数据来访问。
当我定义一个指针而不初始化它时:
int *pi;
它指向内存的随机部分。
当我定义一个指向指针的指针而不初始化它时会发生什么?
int **ppi;
它指向哪里?它应该指向另一个指针,但我没有定义它所以它可能指向内存的随机部分?如果可以的话,能不能举例说明一下区别?
为了清楚起见,请考虑以下声明
T *ptr;
其中 T
是某种类型说明符。如果声明的变量 ptr
具有自动存储持续时间,则指针既不显式也不隐式地初始化。
因此指针具有不确定的值。
T
可以是任何类型。您可以将 T 定义为例如
typedef int T;
或
typedef int *T;
指针是标量对象。因此在这个声明中
typedef int *T;
T *ptr;
指针 ptr
具有不确定的值,与声明中的方式相同
typedef int T;
T *ptr;
任何未初始化的局部变量都包含不确定的值。不分类型。例如,未初始化的 int
、int*
或 int**
.
但是,C 中有一条规则说,如果您不访问此类未初始化局部变量的地址,而是使用它的值,则会调用未定义的行为——这意味着错误,可能是崩溃等。理由很可能是这些变量可能在寄存器中分配并且没有可寻址的内存位置。有关详细信息,请参阅 。
所以下面这些例子都是错误的,因为局部变量本身的地址从未被使用过:
{
int i;
int* ip;
int** ipp;
printf("%d\n, i); // undefined behavior
printf("%p\n, (void*)ip); // undefined behavior
printf("%p\n, (void*)ipp); // undefined behavior
}
但是,如果你取某处变量的地址,C 就不那么严格了。在这种情况下,您最终会得到一个 不确定值 的变量,这意味着它可能包含任何内容,并且如果您多次访问它,该值可能会不一致。如果是指针,这可能是一个“随机地址”,但不一定如此。
不确定的值可能是所谓的“陷阱表示”,即该类型的禁止二进制序列。在这种情况下,访问变量(读取或写入)会调用未定义的行为。这不太可能发生在普通的 int
上,除非你有一个非常奇特的系统,它不使用 2 的补码——因为在标准的 2 的补码系统中,int
的所有值组合都是有效的,并且有无填充位、负零等
示例(假设为 2 的补码):
{
int i;
int* ip = &i;
printf("%d\n", *ip); // unspecified behavior, might print anything
}
未指定的行为 意味着编译器不需要记录行为。您可以获得任何类型的输出,并且不需要保持一致。但至少程序不会像在 未定义行为.
的情况下那样崩溃和烧毁但是陷阱表示更可能是指针变量的事情。特定的 CPU 可以有一个受限地址 space 或者在低级初始化时,MMU 可以设置为将某些区域设置为虚拟区域,某些区域仅包含数据或某些区域仅包含代码等. 即使您将无效地址值读入变址寄存器,这样的 CPU 也可能会产生硬件异常。如果您尝试通过无效地址访问内存,它肯定很可能会这样做。
例如,MMU 可能会阻止试图从内存的数据段执行代码的失控代码,或者将代码内存的内容当作数据来访问。