比较已经释放的指针会调用UB吗?

时间:2018-10-03 14:00:38

标签: c undefined-behavior hexchat

这似乎是一种相当常见的模式,例如在hexchat中(可能无法编译,另请参见plugin docs。还请注意,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将(可能释放-在此示例中绝对释放,请参见do_ub中的注释)指针与另一个指针进行比较(如果您遵循{ {3}}您将最终进入here (plugin.c#L1089, hexchat_set_context)。要调用此代码,请在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吗?

3 个答案:

答案 0 :(得分:6)

如指向C11 Standard draft 6.2.4p2 (Storage durations of objects)中所述,在指向该对象的对象达到其生命周期结束后使用指针的值是 indeterminate (重点是我的):

  

对象的生存期是程序执行期间的一部分   保证为其保留哪个存储。存在一个对象,   具有恒定的地址,并保留其最后存储的值   整个生命周期如果在对象之外引用对象   寿命,行为是不确定的。 指针的值变为   不确定,当它指向(或刚刚过去)的对象到达   生命的尽头。

使用它的值(仅适用于任何事物)是明确的未定义行为,如Annex J.2(未定义行为)所述:

  

在以下情况下,行为未定义:[...]   使用指向其生命周期已结束的对象的指针的值   (6.2.4)。

答案 1 :(得分:2)

是的,严格来说,使用已经为任何事情释放的指针值-甚至看似无害的比较-严格来说是未定义的行为。在实践中不太可能造成任何实际问题,但我要避免。

另请参见C FAQ列表question 7.21

答案 2 :(得分:0)

tl; dr:能够执行某些操作(例如,对指针进行比较)而无需考虑所标识的对象的生存时间的能力是一个流行的扩展,可以将大多数编译器配置为在禁用优化的情况下支持该功能。但是,标准并没有要求对此提供支持,并且积极的优化器可能会破坏依赖该标准的代码。

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

尽管该标准的作者无疑意识到能够与任意指针进行比较(需要注意的是,结果可能并不特别有意义)是每个针对常规硬件的实现都支持的有用功能,但他们看到了无需将其视为高质量实现所支持的“通用扩展”,这将是有益且实用的。

根据C89基本原理第11页第23行:

  

术语未指定行为,未定义行为和实现定义的行为是   用于对编写其标准不具有的属性的程序的结果进行分类,或者   无法完全描述。采用此分类的目的是允许   实施中的多样性,使实施的质量成为   以及允许某些流行的扩展,而不会消除   符合标准。标准的信息性附录J对这些行为进行了分类   属于这三类之一。

不幸的是,即使当今使用的几乎所有平台都可以以基本上为零的成本支持这种语义,但是一些编译器作者仍希望将代码永远不会对释放指针执行任何操作,这比程序员从中获得的任何价值都更为重要。在传统平台上曾经获得了普遍支持的扩展。除非可以保证任何使用自己代码的人都将禁用由渴望摆脱有用的扩展语言的过分热衷的优化器的作者强加的假性“优化”,否则一个人可能不得不编写额外的代码来解决缺少此类扩展的问题