首先,澄清一下,我不谈论取消引用无效指针!
考虑以下两个例子。
示例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?
问题
上述任何一个例子都会调用未定义的行为吗?
上下文
这个问题是针对this discussion提出的。建议是,例如,指针参数可以通过x86段寄存器传递给函数,这可能会导致硬件异常。
根据C99标准,我们学习以下内容(强调我的):
[3.17] 不确定值 - 未指定的值或陷阱表示
然后:
[6.2.4 p2] 当指针的值变为不确定时 它指向的对象到达其生命周期的末尾。
然后:
[6.2.6.1 p5] 某些对象表示不需要表示对象类型的值。如果对象的存储值具有这样的表示,并且由不具有字符类型的左值表达式读取,则行为未定义。如果这样的表示是由副作用产生的,该副作用通过不具有字符类型的左值表达式修改对象的全部或任何部分,则行为是未定义的。这种表示称为陷阱表示。
综合所有这些,我们对访问“死”对象的指针有什么限制?
附录
虽然我引用了上述C99标准,但我很想知道任何C ++标准中的行为是否不同。
答案 0 :(得分:30)
示例2无效。你问题中的分析是正确的。
示例1有效。结构类型永远不会保留陷阱表示,即使其成员之一也存在。这意味着在陷阱表示会导致问题的系统上的结构分配必须实现为逐字节副本,而不是逐个成员的副本。
6.2.6类型表示
6.2.6.1一般
6 [...]结构或联合对象的价值永远不会说 表示,即使结构或联合对象的成员的值可能是 陷阱表示。
答案 1 :(得分:15)
我的解释是,虽然只有非字符类型可以有陷阱表示,但任何类型都可以具有不确定的值,并且以任何方式访问具有不确定值的对象会调用未定义的行为。最臭名昭着的例子可能是OpenSSL无效使用未初始化的对象作为随机种子。
所以,你的问题的答案是:永远不会。
顺便说一句,不仅仅是指向对象而且指针本身在free
或realloc
之后不确定的有趣结果是这个成语调用了未定义的行为:
void *tmp = realloc(ptr, newsize);
if (tmp != ptr) {
/* ... */
}
答案 2 :(得分:-1)
简答:在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) {
}
所以你正在将值作为指针访问,而不是一堆字节。
如果指针值无效(未初始化,释放),则不会定义行为。