54

首先,澄清一下,我不是在谈论取消引用无效指针!

考虑以下两个示例。

示例 1

typedef struct { int *p; } T;

T a = { malloc(sizeof(int) };
free(a.p);  // a.p is now indeterminate?
T b = a;    // Access through a non-character type?

示例 2

void foo(int *p) {}

int *p = malloc(sizeof(int));
free(p);   // p is now indeterminate?
foo(p);    // Access through a non-character type?

问题

上述任何一个示例都调用未定义的行为吗?

语境

这个问题是针对这个讨论提出的。建议是,例如,指针参数可以通过 x86 段寄存器传递给函数,这可能会导致硬件异常。

从 C99 标准中,我们学到了以下内容(重点是我的):

[3.17] 不确定值- 未指定的值或陷阱表示

进而:

[6.2.4 p2]当指针指向的对象到达其生命周期的末尾时,指针的值变得不确定。

进而:

[6.2.6.1 p5]某些对象表示不需要表示对象类型的值。如果对象的存储值具有这样的表示形式并且由不具有字符类型的左值表达式读取,则行为是 undefined。如果这种表示是由通过不具有字符类型的左值表达式修改对象的全部或任何部分的副作用产生的,则行为未定义。这种表示称为陷阱表示

综上所述,我们对访问“死”对象的指针有什么限制?

附录

虽然我在上面引用了 C99 标准,但我很想知道任何 C++ 标准中的行为是否不同。

4

3 回答 3

31

示例 2 无效。你问题中的分析是正确的。

示例 1 有效。一个结构类型从不拥有陷阱表示,即使它的一个成员拥有。这意味着在陷阱表示会导致问题的系统上,结构分配必须实现为按字节复制,而不是逐个成员复制。

6.2.6 类型的表示

6.2.6.1 概述

6 [...] 结构或联合对象的值永远不会以 rap 表示,即使结构或联合对象的成员的值可能是陷阱表示。

于 2013-06-10T13:40:03.550 回答
15

我的解释是,虽然只有非字符类型可以具有陷阱表示,但任何类型都可以具有不确定的值,并且以任何方式访问具有不确定值的对象都会调用未定义的行为。最臭名昭著的例子可能是 OpenSSL 将未初始化的对象无效地用作随机种子。

所以,你的问题的答案是:从不。

顺便说一句,一个有趣的结果不仅是指向的对象,而且指针本身free在or之后是不确定realloc的,这个习惯用法会调用未定义的行为:

void *tmp = realloc(ptr, newsize);
if (tmp != ptr) {
    /* ... */
}
于 2013-06-10T13:33:30.340 回答
-1

C++ 讨论

简短的回答:在 C++ 中,没有访问“读取”类实例这样的事情。您只能“读取”非类对象,这是通过左值到右值的转换来完成的。

详细解答:

typedef struct { int *p; } T;

T指定一个未命名的类。为了便于讨论,我们将这个类命名为T

struct T {
    int *p; 
};

因为你没有声明复制构造函数,编译器隐式声明了一个,所以类定义如下:

struct T {
    int *p; 
    T (const T&);
};

所以我们有:

T a;
T b = a;    // Access through a non-character type?

确实是的; 这是拷贝构造函数的初始化,所以拷贝构造函数的定义会由编译器生成;定义等价于

inline T::T (const T& rhs) 
    : p(rhs.p) {
}

因此,您将值作为指针访问,而不是一堆字节。

如果指针值无效(未初始化、已释放),则未定义行为。

于 2013-06-16T04:23:42.470 回答