是否可以为指向非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
}
答案 0 :(得分:7)
TL; DR:否。
你要求的是,基本上,C ++是由引用提供的。 C没有直接这样的东西(虽然见下文)。
尽管如此,即使在C ++中,也没有运行时保证引用有效。没有什么可以阻止您从空指针初始化引用。它只是未定义的行为,程序员有责任不编写可以执行此操作的代码。
因此,解决方案是永远不要使用带有NULL值的thia类型的指针。使用工厂函数始终生成对象,并始终在定义时初始化指针,并将指针包装在struct中,并使用assert是帮助程序员在此中不失败的好工具。
编辑:实际上this other answer提供了一种或多或少等于C ++引用的方式,如果你有那里描述的那种数组参数,那么通过调用你的函数,调用者promises参数将不是NULL指针。但这不是硬检查,实际上并没有阻止任何事情,只是让编译器能够更频繁地警告你。
答案 1 :(得分:7)
是的,C有一个声明指针参数的方法,该参数不能为空。但是,它不提供您可能想要的行为。它建议编译器可以编译例程,期望参数永远不会为null,但它不一定会阻止调用例程错误地传递null。编译器可能会检测到对此类例程的一些不正确的调用,并发出有关它们的警告或错误,但无法检测到其他例程。
这是通过将static
关键字放在参数声明中的很少使用的语法来完成的:
void foo(int p[static 1])
{
… body of function …
}
这表明p
必须提供对至少一个元素的数组的第一个元素的访问权限,根据C 2011 [N1570] 6.7.6.3 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); }
因此,只有在直接传递此参数时,此编译器才能检测到此类参数的错误传递。
通过删除函数中包含的运行时检查,声明具有此属性的参数可能会对代码产生负面影响。当我编译它时:
void foo(int p[static 1])
{
if (!p)
{
fprintf(stderr, "Invalid pointer passed to foo.\n");
exit(EXIT_FAILURE);
}
}
然后编译器完全从程序中删除fprintf
和exit
。这是因为编译器知道p
不能为空,因此!p
永远不会为真,因此fprintf
和exit
永远不会在正确的程序中执行。因此编译器会在优化过程中删除它们。
答案 2 :(得分:1)
不,不可能。使用属性nonnull进行编译时检查并在函数开头添加断言。断言只是一行,有助于调试。比较:
output_type *some_function(nonnull_input_type *input) {
和
output_type *some_function(NONNUL input_type *input) {
ASSERT(input);
打字并不多。除了ASSERT宏之外,还可以重新定义一些有意义的东西,比如longjump to safe frame如果你的程序需要优雅的崩溃。