【问题标题】:Does casting a T pointer to a T' pointer and back yield the original pointer if T' is an incomplete type?如果 T' 是不完整的类型,是否将 T 指针转换为 T' 指针并返回原始指针?
【发布时间】:2016-11-23 11:44:23
【问题描述】:

考虑以下改编自the Tor source code的sn-p:

/* This can be a malloc wrapper with minimal initialization. */
other_t *make_other(void);

/* This struct is never defined. */
struct undef;
typedef struct undef undef_t;

undef_t *make_undef(void)
{
    other_t *other = make_other();
    return (undef_t*)other;
}

假设程序中的所有undef_t 指针都转换为other_t 指针,并进一步假设所有采用undef_t* 的过程在使用前都将它们转换为other_t*

根据 C99 标准的第 6.3.2.3 节,如果other 未正确对齐为指向undef_t 的指针,但如果它是 em>,将make_undef 的结果转换回other_t* 会产生make_other 返回的原始指针。但是,undef_t 是一个未定义的类型,我找不到任何关于这些的对齐规则。如果定义了 undef_t 并具有正确的对齐方式,这些转换是否仍然有效?

【问题讨论】:

    标签: c pointers casting undefined-behavior


    【解决方案1】:

    我认为类型是否不完整没有任何区别。

    根据 C11 6.2.5.22,只有三种不完全类型可以实例化:

    1. 未知大小的数组类型
    2. 未知内容的结构类型
    3. 未知内容的联合类型

    此外,根据 C11 6.2.5.28:

    所有指向结构类型的指针都应具有相同的表示形式和 对齐要求彼此。所有指向联合类型的指针 应具有与每个相同的表示和对齐要求 其他。

    因此,无论类型是否完整,指向结构或联合的指针的表示和对齐要求都是已知的,因为您仍然知道它是指向某种结构或联合的指针。

    对于数组,因为我们从 C11 6.3.2.1.3 知道:

    类型为“类型数组”的表达式被转换为 类型为“类型指针”的表达式,指向初始 数组对象的元素

    那么我们可以得出结论,指向int 的指针和指向int 数组的指针必须具有相同的对齐要求,因为您可以使用其中任何一个来引用同一个对象。换句话说,所有指向int 数组的指针都具有相同的对齐要求,无论大小是否已知。

    所以如果你知道你有一个指向结构的指针,或者你知道你有一个指向联合的指针,或者你知道你有一个指向指定类型数组的指针,那么你就知道对齐要求是什么.类型不完整的事实并不重要。事实上,如果这不是真的,不完整的类型将没有用处,因为您永远无法可靠地使用指向它们的指针。

    在你的情况下,undef_tother_t 在你的翻译单元中都是typedefd somewhere,所以你知道你有什么样的不完整类型,因此你知道对齐方式对它的要求,因此类型不完整的事实不会产生歧义。当然,如果您作为程序员使用它们而没有自己发现这个问题,那么您仍然会遇到问题,例如other_t 是一个指向联合的指针并且undef_t 是指向结构的指针。但这只是在可能具有不同对齐要求的指针之间进行转换的正常问题 - 由于多个指向类型中的一个不完整,这个问题并没有变得更加困难。

    编辑 - 基于 cmets 的进一步说明:

    “指向不完整类型的指针”只能指向完整类型的对象,因为根据定义显然不能实例化不完整类型(为简洁起见,我将malloc()ing 的可能性放在一边任意数量的内存并指向一个不完整类型的指针)。

    存在指向不完整类型的指针是为了能够拥有和传递指向聚合和复合类型的指针,而无需提供可用的类型定义。换句话说,当您没有足够的信息来实例化特定类型时,但您确实有足够的信息来指向一个特定类型。不透明类型在此基础上工作,所有实际工作都在类型定义可用的函数中完成,使用这些类型的代码只需要能够存储对象的地址,因此它可以在这些函数之间传递。

    在对齐方面,指针的表示可以根据它指向的对象的对齐要求而改变。例如,正如 cmets 中所指出的,如果所有 ints 必须存储在 8 字节边界上,那么指针表示可能不会存储 3 个最低有效位,因为它们总是为零。但是,如果您随后尝试将 char 指针转换为 int 指针并返回,您可能会丢失信息,因为 char 指针必须能够指向单个字节,并且其中一些信息会丢失此假设转换为int *

    同样,某些小型结构可能会在 4 字节边界上对齐,而较大的结构可能会在 32 字节边界上对齐。但是指向结构的指针的对齐要求必须全部相同,这正是因为这些指向不完整类型的指针。在您使用指向不完整结构类型的指针的地方,您没有可用的类型定义(如果有,它将是一个完整的类型),因此您不知道是否可以忽略最不重要的例如,5 位或仅最低有效 3 位。因此,指向不完整结构类型的指针的对齐要求必须足够宽松,以便它们可以正确地保存任何可能的结构类型的位置。为了使传递这些更容易,我们可以从上面的引用中看到,C 要求 all 指向结构类型的指针具有相同的对齐要求。例如,某些结构实际上可能仍然在 32 字节边界上对齐,但是不能允许指向该结构的指针充分利用这一点,并且它必须能够保存任何结构的位置输入。

    但是,例如,如果所有结构类型都在不小于 4 字节的边界上对齐,那么对于指向结构的指针(包括指向不完整结构类型的指针)忽略最低有效 3 位是完全可以的,因为您可以这样做的同时仍然确保您可以安全地表示任何结构的位置。事实上,C 要求所有指向结构类型的指针具有相同的对齐要求,并要求所有指向联合类型的指针具有相同的对齐要求,但 要求指向的指针的对齐要求结构类型与指向联合类型的指针的对齐要求相同,表明可能,例如,指向结构的指针依赖于最小 4 字节边界,但指向联合的指针依赖于最小 8 字节边界。在这种情况下,您不能安全地将指向不完整结构类型的指针转​​换为指向不完整联合类型的指针,然后再转换回来。

    这就是为什么说“不完整类型没有对齐要求”是不正确的,就像另一个答案目前所做的那样。但它确实意味着不完整的类型对于原始问题没有问题,因为正如已经解释过的,当你有一个不完整的类型时,你至少知道它是一个结构,还是一个联合,还是一个数组,这就是您完全了解对齐要求所需的全部内容。

    【讨论】:

    • @SamuelPeter:这里有太多信息进入 cmets,所以我在回答中添加了更多解释。简短的回应:我确实阅读了您的答案,但我认为它不对,这就是为什么我写了一个不同的答案。我认为您关于“不完整类型没有对齐要求”的断言是错误的。
    • "根据 C11 6.2.5.22,只有三种不完整类型" -- 该列表并不详尽。 void 也是不完整类型(无法完成的类型)。
    • @KeithThompson :公平的评论,我更新了答案,可以证实的是唯一感兴趣的,因为虽然你可以有一个空指针,但你显然不能指向它在一个空对象。
    猜你喜欢
    • 1970-01-01
    • 2019-08-21
    • 2013-10-20
    • 2013-02-17
    • 2011-12-10
    • 1970-01-01
    • 2021-06-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多