C 是否可以为非 NULL 指针定义类型?

C is it possible to define a type for not NULL pointers?

是否可以为指向非 NULL 的地址的指针定义类型?

背景:

我经常写像-

这样的函数
output_type *some_function(input_type *input) {
//confirm input and its members were correctly initialised
if (input == NULL) {
// error handling code
}
if (input->member_which_is_a_pointer == NULL) {
// error handling code
}
...
//return output computed from input
}

如果可以改为编码以下内容会更清楚-

output_type *some_function(input_type_not_null *input) {
//return output computed from input
}

TL;DR: 没有。

本质上,您所要求的是 C++ 中由引用提供的内容。 C直接没有这样的东西(尽管见下文)。

尽管如此,即使在 C++ 中,也没有运行时保证引用有效。没有什么能阻止您从空指针初始化引用。这只是未定义的行为,程序员有责任不编写可能执行此操作的代码。

因此,解决方案是永远不要让 thia 类型的指针具有 NULL 值。始终使用工厂函数生成对象,始终在定义时初始化指针,将指针包装在结构中,并使用断言是帮助程序员在这方面不失败的好工具。


编辑:实际上 提供了一种 more-or-less 等同于 C++ 引用的方式,如果您具有此处描述的那种数组参数,那么通过调用您的函数,调用者承诺参数不会是 NULL 指针。但这不是硬检查,实际上并没有阻止任何事情,只是让编译器能够更频繁地警告你。

不,不可能。使用属性 nonnull 进行编译时检查,并在函数的开头添加一个断言。断言只是一行,对调试有很大帮助。比较:

output_type *some_function(nonnull_input_type *input) {

output_type *some_function(NONNUL input_type *input) {
   ASSERT(input);

打字没那么多。除了 ASSERT 宏可以被重新定义来做一些有意义的事情,比如如果你的程序需要优雅的崩溃,longjump 到安全框架。

是的,C 有一个方法来声明一个不能为空的指针参数。但是,它不提供您可能想要的行为。它建议编译器它可以在参数永远不会为 null 的情况下编译例程,但它不一定会阻止调用例程错误地传递 null。编译器可能会检测到对此类例程的一些错误调用,并发出有关它们的警告或错误,但无法检测到其他调用。

这是通过将 static 关键字放在参数声明中的很少使用的语法来完成的:

void foo(int p[static 1])
{
    … body of function …
}

根据 C 2011 [N1570] 6.7.6.3 7,这表示 p 必须提供对至少一个元素的数组的第一个元素的访问权 7. 因为必须有一个元素 p 点,p 不能为空。例如,编译这段代码时:

#include <stddef.h>

void foo(int p[static 1]);

void bar(void) { foo(NULL); }

使用带有默认开关的 Apple LLVM 9.0.0 (clang-900.0.39.2),编译器警告:

x.c:5:18: warning: null passed to a callee that requires a non-null argument
      [-Wnonnull]
void bar(void) { foo(NULL); }
                 ^   ~~~~
x.c:3:14: note: callee declares array parameter as static here
void foo(int p[static 1]) {}
             ^~~~~~~~~~~

但是,此代码编译时没有警告:

#include <stddef.h>

void foo(int p[static 1]) {}

void bar(int *p) { foo(p); }

void baz(void) { bar(NULL); }

看来这个编译器只有在直接传递null的情况下才能检测到null被错误传递了。

通过删除包含在函数中的 run-time 检查,声明具有此属性的参数可能会对您的代码产生不利影响。当我编译这个时:

void foo(int p[static 1])
{
    if (!p)
    {
        fprintf(stderr, "Invalid pointer passed to foo.\n");
        exit(EXIT_FAILURE);
    }
}

然后编译器从程序中完全删除 fprintfexit。这是因为编译器知道 p 一定不能为空,所以 !p 永远不会为真,所以 fprintfexit 永远不会在正确的程序中执行。所以编译器在优化过程中删除了它们。