c语言 - 比较已释放的指针是否会调用 UB?



这似乎是一种相当常见的模式,例如在hexchat中(可能不会编译,另请参阅插件文档。请注意,hexchat_plugin_get_info并没有永远使用过,所以为了简单起见,我省略了它):

static hexchat_plugin *ph;
static int timer_cb(void *userdata) {
if (hexchat_set_context(ph, userdata)) { /* <-- is this line UB? */
/* omitted */
}
return 0;
}
static int do_ub(char *word[], char *word_eol[], void *userdata) {
void *context = hexchat_get_context(ph);
hexchat_hook_timer(ph, 1000, timer_cb, context);
hexchat_command(ph, "close"); /* free the context - in practice this would be done by another plugin or by the user, not like this, but for the purposes of this example this simulates the user closing the context. */
return HEXCHAT_EAT_ALL;
}
int hexchat_plugin_init(hexchat_plugin *plugin_handle, char **plugin_name, char **plugin_desc, char **plugin_version, char *arg) {
*plugin_name = "do_ub";
*plugin_desc = "does ub when you /do_ub";
*plugin_version = "1.0.0";
ph = plugin_handle;
/* etc */
hexchat_hook_command(ph, "do_ub", 0, do_ub, "does UB", NULL);
return 1;
}

timer_cb中的行导致hexchat将(在本例中可能是free'd-肯定是free'dd,请参阅do_ub中的注释)指针与另一个指针进行比较,如果您从这里开始(plugin.c#L1089,hexchat_set_context),您将在这里结束(hexchat.c#L191,is_session)。要调用此代码,请在hexchat中运行/do_ub

相关代码:

int
hexchat_set_context (hexchat_plugin *ph, hexchat_context *context)
{
if (is_session (context))
{
ph->context = context;
return 1;
}
return 0;
}
int
is_session (session * sess)
{
return g_slist_find (sess_list, sess) ? 1 : 0;
}

这是UB的事吗?

如C11标准草案6.2.4p2(对象的存储持续时间)所述,在所指向的对象达到其生命周期结束后使用指针值是不确定的(重点是我的):

对象的生存期是在该存储被保证为其保留。对象存在,具有一个常量地址,并保留其上次存储的值在其整个生命周期中。如果一个对象被引用到其外部终身,行为是未定义的指针的值变为不确定当它指向(或刚刚经过)的对象到达它的寿命结束。

使用它的值(仅用于任何事情)是一种明确的未定义行为,如附录J.2(未定义行为):所述

在以下情况下行为是未定义的:〔…〕使用指向生存期已结束的对象的指针的值(6.2.4).

是的,严格来说,使用为任何东西释放的指针值——即使是看似无害的比较——也是未定义的行为。这不太可能在实践中造成任何实际问题,但我认为值得避免。

另请参见C常见问题列表,问题7.21。

tl;dr:在不考虑由此识别的对象的生存期的情况下对指针执行某些操作(如比较)的能力是一种流行的扩展,绝大多数编译器都可以配置为在禁用优化的情况下支持该扩展。然而,标准并没有强制要求支持它,激进的优化器可能会破坏依赖它的代码。

在编写标准时,有一些分段内存平台,试图将指针加载到寄存器中会导致系统检索有关指针所在内存区域的信息。如果此类信息不再可用,则检索该信息的尝试可能会在本标准管辖范围之外产生任意后果。标准要求涉及此类指针的比较除了产生0或1之外没有任何副作用,这将使该语言在此类平台上不切实际。

尽管该标准的作者毫无疑问地意识到,能够使用与任意指针的比较(需要注意的是,结果可能没有特别的意义)是每个针对传统硬件的实现所支持的有用功能,他们认为没有必要将其视为一个"流行的扩展",只要这样做是有用和实用的,那么高质量的实现就会支持它。

根据C89原理,第11页第23行:

术语未指定行为、未定义行为和实现定义行为用于对编写程序的结果进行分类,而这些程序的属性本标准没有,或者无法完全描述。采用这种分类的目的是允许实现方式的多样性,使实现质量成为市场,并允许某些流行的扩展,而不删除符合标准。本标准的资料性附录J列出了这些行为属于这三类中的一类。

不幸的是,尽管目前使用的几乎所有平台都可以以基本上零成本(*)支持这种语义,但一些编译器作者认为,他们希望假设代码永远不会用释放的指针做任何事情,这比程序员从传统平台上基本上普遍支持的扩展中获得的任何值都更重要。除非可以保证任何使用自己代码的人都会禁用那些急于消除语言中有用扩展的优化器的作者强加的虚假"优化",否则可能必须编写额外的代码来解决没有此类扩展的问题。

(*)在某些情况下,函数向外部代码暴露指向其已分配和释放的存储区域的多个指针,因此,必须维护行为保证的编译器将需要实际执行会泄露指针的存储操作;将指针视为不确定将允许消除存储。然而,在人为的场景之外,通过使用泄露给外部世界的指针来消除此类商店所节省的成本很少会对性能产生任何有意义的影响。

最新更新