【问题标题】:Writing a generic function to detect if an array of pointers contains NULL编写一个泛型函数来检测指针数组是否包含 NULL
【发布时间】:2017-02-19 08:47:12
【问题描述】:

我想编写一个通用函数来检测指向某个任意类型的指针数组是否包含NULL。我最初的尝试是这样的:

bool find_null (void *ptrs, size_t num_ptrs) {
    void **array = ptrs;
    size_t i;
    for (i = 0; i < num_ptrs; ++i) {
        if (array[i] == NULL) return true;
    }
    return false;
}

有人指出,这可能会导致严格的别名违规,因为指向 Foo 的指针数组将作为指向 void 的指针数组进行访问,这未列为对象的允许访问方式之一允许在 C.2011 §6.5¶7 中访问。

我可以改写函数以将指针数组访问为unsigned char *,但我不确定如何在不破坏严格别名的情况下执行NULL 检查。有人可以提供有效的技术吗?

bool find_null (void *ptrs, size_t num_ptrs) {
    unsigned char *array = ptrs;
    void *p;
    size_t i;
    for (i = 0; i < num_ptrs; ++i) {
        memcpy(&p, array + i * sizeof(p), sizeof(p));
        if (p == NULL) return true;
        /*
         * Above seems to still break strict aliasing.
         * What should be done instead?
         */
    }
    return false;
}

我们的目标是编写一个与特定类型函数一样工作的通用函数。换句话说,以下函数的通用版本:

bool find_null_Foo (Foo *array[], size_t num_ptrs) {
    size_t i;
    for (i = 0; i < num_ptrs; ++i) {
        if (array[i] == NULL) return true;
    }
    return false;
}

【问题讨论】:

  • 这通常是不可能的。不要求sizeof(void*) 等于sizeof(Foo*)。它们在不可字节寻址的机器上可能是不相等的,其中sizeof(void*)sizeof(Foo*) 稍大一点,因为需要记录指针所引用的字中的字节。
  • 您希望代码只考虑值为(void*)NULL 的空指针,还是要处理平台具有各种空指针值的可能性? (当然,所有空指针都彼此等同——尽管它们可能有不同的编码。)
  • @RaymondChen:感谢您提出这个问题,这是一个有效的观点。我发布了一个答案,应该解释这一点。但是,对于这样的机器,我可能最终会将其设计为 30 位可寻址(对于 4 字节字)或 61 位可寻址(对于 8 字节字),而不是尝试处理不同大小的指针。
  • @jxh 祝 PDP-10 好运。 36 位字可寻址地址空间和 36 位寄存器。
  • @RaymondChen:在这种情况下是 34 位可寻址的。

标签: c pointers language-lawyer void strict-aliasing


【解决方案1】:

不保证泛型函数在指向不同类型的指针可能具有不同表示和/或大小的系统上按预期工作。值得庆幸的是,如今这样的架构非常罕见。例如,Posix 兼容系统保证对所有指针类型使用相同的表示和大小。

【讨论】:

  • 不幸的是,即使在指针大小相同的系统上,违反严格别名仍然会导致编译器产生意外的优化结果。
  • @jxh:这种行为非常违反直觉,应该被视为一个错误。我想知道如何制定严格的混叠以防止产生如此深远的后果。
  • @chqrlie:最简单的公式是指定将指针从T* 类型转换为U* 将授予使用该目标或从它派生的指针作为U* 的许可,直到下次通过不是从上述转换结果派生的任何指针访问对象时。这不会排除许多有用的优化,但可能会避免在目前需要它的 90% 的程序中使用 -fno-strict-aliasing
【解决方案2】:

你不能用你提供的特定界面来做,但你可以用这种有点笨拙的方式来做:

bool find_null (const void *array, size_t num_ptrs, size_t ptr_size,
        const void *null) {
    const char (*ptr_array)[ptr_size] = array;
    size_t i;
    for (i = 0; i < num_ptrs; ++i) {
        if (!memcmp(array[i], null, ptr_size)) return true;
    }
    return false;
}

你会这样称呼它:

struct Foo;

#define ARRAY_SIZE 53
int main(void) {
    struct Foo *my_array[ARRAY_SIZE] = { ... };
    struct Foo * const foo_null = (struct Foo *) 0;
    if (find_null(my_array, ARRAY_SIZE, sizeof(*my_array), &foo_null)) {
        puts("It contains NULL");
    } else {
        puts("It does not contain NULL");
    }
}

请注意,这是假设所讨论类型的空指针只有一种表示形式,这在许多实现中都是正确的,但语言不需要。

还请注意,这实际上并不特定于查找空指针,因此实际上您可以使用它来搜索指针数组以查找任何指针值。事实上,它甚至不特定于指针数组——你可以用它来搜索任何数组的任何值,只要字节对字节相等是一个合适的匹配标准(它不适用于结构或联合体,可能不适用于某些其他类型)。

此外,如果这适合您,那么您可能会设计一个包装宏,使其更容易用于一些更常见的场景。

【讨论】:

  • 不保证你可以比较不同大小的空指针会memcmp,。实际上不能保证sizeof(void*) &gt;= size of(int*)
  • @chqrlie 是正确的。这就是为什么我提出的函数接受一个 指向 空指针值的指针来与数组元素进行比较,并使用memcmp() 来执行比较。我正在比较相同类型的指针。
  • 哎呀,你确实这样做了,但正如你提到的,似乎不能保证任何给定类型的空指针都有唯一的表示。
【解决方案3】:

基于Raymond's comment,通用函数似乎需要额外的参数。由于无法将指针的unsigned char 表示形式转换为void *,因此必须通过回调来完成。

bool find_null_generic (const void *ptrs, size_t ptr_sz, size_t num_ptrs,
                        const void *(*convert)(const void *)) {
    const unsigned char *array = ptrs;
    size_t i;
    for (i = 0; i < num_ptrs; ++i) {
        if (convert(array + i * ptr_sz) == NULL) return true;
    }
    return false;
}

对于指向Foo的假设数组:

const void *convert_Foo (const void *data) {
    const Foo *foo;
    memcpy(&foo, data, sizeof(foo));
    return foo;
}

Foo *foo_array[N] = {...};
bool result = find_null_generic(foo_array, sizeof(Foo *), N, convert_Foo);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-06-23
    • 1970-01-01
    • 2020-03-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多