在一个库里,我应该检查指针是否有效吗?

6

我已经思考了一段时间,甚至质疑在我的自己的库中检查指针是否有效是否真的有必要。如果使用库的用户没有传递正确的指针,我是否应该期望他们自己承担这个责任呢?

例如,如果我有一个分配并返回结构体的库。

struct some_context{
    uintptr_t somedata;
};
struct some_context* createsomecontext(void){
    return malloc(sizeof(struct some_context));
}

但是我想对那个结构进行操作

void dosomethingwithsomecontext(struct some_context* ctx){
    //which is more valid here?
    assert(ctx);
    
    //or
    if(!ctx)
        return;
    
    //do something with ctx
}

调用 assert 还是检查结构是否有效并在无效时返回更合理? 我是否不必担心检查指针,只需假设用户将做正确的事情? 同样的情况也适用于清理。

void destroysomecontext(struct some_context* ctx){
    //Which is more valid?
    assert(ctx);
    
    //or
    if(!ctx)
        return;
    
    //do cleanup
}

什么更适合?assert,if还是两者都不用?同样的,如果我要编写一个操作字符串的函数呢?
void dosomestringoperation(char* str){
    assert(str);
    //or
    if(!str)
        return;
    
}

库是否应该进行这些检查?至少在用户模式下,我可以理解在内核中,由于每个设备都可以访问全局内存并且可以通过不进行检查来导致崩溃,但这是否意味着即使用户模式下的应用程序也应该出于安全/健全原因进行检查?


1
如果您想保证安全,那就由您决定。但是使用assert是一个相当不错的折衷方案,因为在发布模式下它不会执行任何操作(如果定义了NDEBUG),所以在这种情况下您不需要进行额外的检查。但是如果您将它放弃,特别是如果该部分对用户不可见,您应该进行检查。但是简单的返回值可能有点过于随意。您可以在成功时返回一个正数,失败时返回负数。 - simre
哦,我其实不知道 assert 只在调试时使用!但例外的安全方面呢?我知道软件通常会因为未能检查指针或字符串操作中未正确考虑长度而遭到利用?仅使用 assert 而不进行检查可能会导致安全漏洞吗? - Kayla
这与任何其他的前置条件检查没有区别。你是否执行它取决于你自己,但是执行检查然后在失败时悄悄返回毫无意义。 - n. m.
3
您可以创建两个库:libKaylalibKayla_dbg,其中“debug”版本有assert和额外的检查。常规库“在使用意外参数调用时会引发未定义行为”。 - pmg
“但如果有人传递给我一个错误的指针,我该怎么办?” 你应该崩溃。不,真的。 - GSerg
显示剩余5条评论
1个回答

3

通常人们认为指针是有效的,特别是除了空指针之外,你没有办法确定传递的指针是否真正有效(如果你得到一个指向未分配内存或错误数据的指针怎么办?)。

一个断言(即使在发布版本中也可能是活动的)对于您的调用者来说是一个很好的礼貌,但也仅仅如此;当尝试解引用时,您可能仍然会崩溃,所以无论如何都会出问题。

但是,如果您获得空指针,则绝不能静默返回:这样会将一个逻辑错误隐藏起来,使调用者更难进行调试。


无论如何,如果您得到一个空指针,请不要默默地返回:您正在隐藏一个逻辑错误,这会使调用者更难以调试。这就是为什么在代码中void返回是真正的问题 - 没有办法返回错误条件。在我看来,assert()有两个致命缺陷:首先,它不能被依赖,因为如果使用NDEBUG定义编译,则会消失;其次,崩溃整个进程而不是优雅地处理错误是极端严重的。我认为任何旨在在错误输入时崩溃整个进程的库都是一个巨大的失败。 - Andrew Henle
如果您要检查返回值,可以先检查指针。 - Matteo Italia
代码总是没有错误的吗?只有错误输入才是导致程序失败的原因吗?所以,如果有人没有检查一个微不足道的指针,就崩溃整个进程,而不是返回一个失败代码?因为返回错误代码、记录错误,并具有调用者可以检索错误消息以了解出了什么问题的功能太难了? - Andrew Henle
2
“我会认为任何旨在在错误输入时崩溃整个进程的库都是一堆大便。” - 那么,比如strcpy?或者说,整个C标准库?不确定每个人都会称之为“一堆大便”。 - IInspectable
2
我的客户通常收到已定义NDEBUG的编译代码,因此我可能放置的任何assert()也会消失。说到这里,我甚至没有建议完全不使用aasert()。我是在明显的指出,"垃圾进,垃圾出" 是一种常见的策略,甚至是语言支持库所采用的。我没有看到C标准库的供应商有必要证明将NULL传递给 strcpy() 可能会终止调用过程。如果你很幸运的话,这样做可能还行。 - IInspectable
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接