简体   繁体   English

在C中返回函数值的方法

[英]Methods of returning function values in C

When writing functions in C, what considerations govern the way in which results are returned from the function? 在C中编写函数时,有哪些考虑因素决定了函数返回结果的方式? My question arises from reading the following code, which creates a node in a doubly-linked list, and which comes from this book: 我的问题是从阅读下面的代码,它创建一个双向链表中的节点,以及来自本书:

typedef struct DLLIST
{
    uint16 tag;                 /* This node's object type tag */
    struct DLLIST *next;
    struct DLLIST *previous;
    void *object;
    uint32 size;                /* This node's object size */
} DLLIST;

DLLIST *dlCreate(uint16 tag, void *object, uint32 size)
{
    DLLIST *newNode;

    newNode = malloc(sizeof(*newNode));

    if (newNode != NULL)
    {
        newNode->previous = NULL;
        newNode->next = NULL;
        newNode->tag = tag;
        newNode->size = size;
        newNode->object = malloc(size);

        if (newNode->object != NULL)
        {
            memcpy(newNode->object, object, size);
        }
        else
        {
            free(newNode);
            newNode = NULL;
        }
    }

    return newNode;
}

Indeed, there are many aspects of this function that as a beginner I find perplexing: 事实上,这个功能有很多方面,作为一个初学者,我觉得困惑:

  1. Why is the DLLIST pointer returned this way, rather than being passed as a reference pointer and modified? 为什么DLLIST指针以这种方式返回,而不是作为引用指针传递并修改? If you did this, you could free up the return value for use as a status byte that reported function success/failure. 如果这样做,您可以释放返回值,以用作报告功能成功/失败的状态字节。
  2. Why is the second argument not tested to ensure that the pointer is not NULL? 为什么没有测试第二个参数以确保指针不是NULL? Surely passing a NULL pointer to memcpy would be a very bad thing? 肯定将一个NULL指针传递给memcpy将是一件非常糟糕的事情?

Furthermore, is it common practice to force ALL functions within a module to use the same calling convention? 此外,通常的做法是强制模块中的所有函数使用相同的调用约定吗? For example, should all functions within the doubly-linked list module from which the function above is taken conform to status byte = function(argument list) ? 例如,双向链表模块中所有上述函数的函数是否都符合status byte = function(argument list) In addition, is it common practice to validate ALL function arguments whose value may be incorrect? 此外,通常的做法是验证值可能不正确的所有函数参数吗? For example, check all pointer values to ensure they are not null? 例如,检查所有指针值以确保它们不为空?

It's all rather subjective, I have to say. 我不得不说,这一切都相当主观。

1) Why is the DLLIST pointer returned this way, rather than being passed as a reference pointer and modified? 1)为什么DLLIST指针以这种方式返回,而不是作为引用指针传递并修改? If you did this, you could free up the return value for use as a status byte that reported function success/failure. 如果这样做,您可以释放返回值,以用作报告功能成功/失败的状态字节。

This is somewhat a matter of taste. 这有点像味道。 I prefer to pass a pointer to the function so that the caller manages the memory, although returning a dynamically allocated pointer is also valid. 我更喜欢传递指向函数的指针,以便调用者管理内存,尽管返回动态分配的指针也是有效的。 In some cases, it's better and simpler, depending on the API architecture. 在某些情况下,它更好,更简单,具体取决于API架构。 Either way, the return value can be used for success/failure checks, as returning a NULL pointer would indicate failure. 无论哪种方式,返回值都可用于成功/失败检查,因为返回NULL指针将指示失败。

2) Why is the second argument not tested to ensure that the pointer is not NULL? 2)为什么没有测试第二个参数以确保指针不是NULL? Surely passing a NULL pointer to memcpy be a very bad thing? 肯定将一个NULL指针传递给memcpy是一件非常糟糕的事情?

That is a mentality a lot of C programmers have (those I know, anyway) , which also reflects how the language is built. 这是很多C程序员的心态(无论如何我都知道),这也反映了语言的构建方式。 In most C libraries, each function has a contract, and not respecting it invokes undefined behavior. 在大多数C库中,每个函数都有一个契约,而不尊重它会调用未定义的行为。 I rarely bother checking for NULL arguments being passed to functions. 我很少检查传递给函数的NULL参数。 When I do, it's because the NULL pointer changes the behavior of the function, not because I'm making sure the caller respected the contract. 当我这样做时,这是因为NULL指针改变了函数的行为,而不是因为我确保调用者尊重契约。 Some may argue that it's better performance-wise and/or size-wise because it saves a few instructions here and there (especially true on limited hardware, for which C is known for), I mostly do it not to clutter my code with null-checks all over the place. 有些人可能认为它在性能和/或大小方面都更好,因为它在这里和那里保存了一些指令(特别是在有限的硬件上,因为C已知),我主要是这样做,不会使我的代码混乱 - 到处检查。 You can do either, as long as it's properly documented. 你可以做任何一种,只要它被正确记录。

Furthermore, is it common practice to force ALL functions within a module to use the same calling convention? 此外,通常的做法是强制模块中的所有函数使用相同的调用约定吗?

I have to agree with that. 我不得不同意这一点。 I can't speak for others, but I believe generally accepted as good practice. 我不能代表别人说话,但我认为普遍认为这是一种好的做法。 Switching calling conventions between functions in the same API can confuse programmers and have them waste time checking the documentation over and over. 在同一个API中切换函数之间的调用约定可能会使程序员感到困惑,并且会浪费时间反复检查文档。

In addition, is it common practice to validate ALL function arguments whose value may be incorrect? 此外,通常的做法是验证值可能不正确的所有函数参数吗?

It is common, but the opposite is probably quite as common (refer to #2 above). 这很常见,但相反的情况可能很常见(参见上面的#2)。

Why is the DLLIST pointer returned this way, rather than being passed as a reference pointer and modified? 为什么DLLIST指针以这种方式返回,而不是作为引用指针传递并修改? If you did this, you could free up the return value for use as a status byte that reported function success/failure. 如果这样做,您可以释放返回值,以用作报告功能成功/失败的状态字节。

Returning the result through a parameter passed-in from the outside is a rather inelegant and cumbersome practice, which should only be used when there's no other way. 通过从外部传入的参数返回结果是一种相当不优雅和繁琐的练习,只有在没有其他方法时才能使用。 It requires the caller to define a recipient object, which is already bad by itself. 它要求调用者定义一个已经很糟糕的收件人对象。 But on top of that the need for additional declarations precludes the use of the function in pure expressions. 但最重要的是,需要额外的声明才能排除在纯表达式中使用该函数。

Most of the time, if you can implement your functionality through a pure function, it should be implemented through a pure function. 大多数情况下,如果您可以通过纯函数实现您的功能,它应该通过纯函数实现。 Ie function parameters are for input, function return value is for output. 即功能参数用于输入,功能返回值用于输出。 This is exactly what you see in this case. 这正是您在这种情况下看到的。

Why is the second argument not tested to ensure that the pointer is not NULL? 为什么没有测试第二个参数以确保指针不是NULL? Surely passing a NULL pointer to memcpy would be a very bad thing? 肯定将一个NULL指针传递给memcpy将是一件非常糟糕的事情?

Well, no argument, it is always a good idea to perform such tests. 嗯,没有争论,执行这样的测试总是一个好主意。 However, the exact method of testing can vary, depending on the "level" of the function in the program hierarchy (from "low level" to "high level"). 但是,确切的测试方法可能会有所不同,具体取决于程序层次结构中函数的“级别”(从“低级别”到“高级别”)。 Should it be a debug-only assertion? 它应该是仅调试断言吗? Should it be a permanent assertion (ie a controlled abort, with error log and "call me maybe" message)? 它应该是一个永久断言(即一个受控制的中止,有错误日志和“给我打电话”的消息)? Should it be a run-time test that implies some meaningful recovery strategy? 它应该是一个运行时测试,暗示一些有意义的恢复策略吗? There's no "one true rule" for answering this questions. 回答这个问题没有“一条真正的规则”。 It is a matter of code design. 这是代码设计的问题。

Is there a meaningful fail-safe strategy for this function that should be executed in situations when the test fails? 对于此功能是否有一个有意义的故障安全策略应该在测试失败的情况下执行? If not, then a run-time test in a low level function would make no sense whatsoever. 如果没有,那么在低级函数中进行运行时测试就没有任何意义。 It would only clutter the code with noise and waste performance. 它只会使代码混乱,产生噪音和浪费。 If this is indeed a "low level" function, then a run-time test with if is not really appropriate here. 如果这确实是一个“低级”函数,那么使用if的运行时测试在这里并不合适。 What is more appropriate is a debug-only assertion assert(object != NULL && size > 0) 更合适的是仅调试断言assert(object != NULL && size > 0)

Note also that the detail that makes the object != NULL test appropriate in dlCreate specifically is that the object value is passed to the library function memcpy , which is known to produce undefined behavior in case of null pointer input. 另请注意,在dlCreate object != NULL测试适当的dlCreate特别是将object值传递给库函数memcpy ,已知该函数在空指针输入时会产生未定义的行为 If instead of memcpy the code used some proprietary my_copy_data function, then the proper place to assert the validity of object would be the innards of my_copy_data . 如果代码不是memcpy代码使用了一些专有的my_copy_data函数,那么断言object有效性的适当位置将是my_copy_data的内部。 The dlCreate function itself does not really care about the validity of object value. dlCreate函数本身并不关心object值的有效性。

Answering question 1: There are three common conventions for return values, and you can pick the one that best fits your application: 回答问题1:返回值有三种常见约定,您可以选择最适合您应用的约定:

  1. Return status values (int). 返回状态值(int)。 Typically negative values indicate error, >=0 success. 通常,负值表示错误,> = 0成功。 For example, the POSIX "open()" function. 例如,POSIX“open()”函数。 If you need to pass some object, you pass a pointer to it (for "IN" parameters, or a pointer to a pointer to it (for "OUT" parameters) 如果需要传递一些对象,则传递一个指向它的指针(用于“IN”参数,或指向指向它的指针(用于“OUT”参数)
  2. For "object-oriented" functions, particularly for functions that allocate something, return a pointer to the object (as in the doubly-linked-list example you give). 对于“面向对象”的函数,特别是对于分配内容的函数,返回指向对象的指针(如您给出的双向链表示例)。 Return NULL for failure. 返回NULL表示失败。 Think POSIX "fopen()" as an example. 以POSIX“fopen()”为例。
  3. Absolute value functions. 绝对值函数。 These are ones where the value does not indicate success or failure, but are the actual result of some computation (think "sin()"). 这些值的值不表示成功或失败,而是某些计算的实际结果(想想“sin()”)。

You can mix and match these styles to your own taste. 您可以根据自己的喜好混合搭配这些款式。 Many libraries have a mixture. 许多图书馆都有混合物。 Try not to make the user of the API have to do contortions to use it! 尽量不要让API的用户不得不做扭曲才能使用它!

For example, using your code above, if you changed the API to 例如,如果您将API更改为,请使用上面的代码

int dlCreate(uint16 tag, void *object, uint32 size, DLLIST **list)

Now a user has TWO ways of figuring out if the file was opened (look at the exit status, AND look at the filled in list pointer), and has to do a lot of extra work to figure out what is the right thiong to do for an error. 现在,用户有两种方法可以确定文件是否已打开(查看退出状态,查看填充的列表指针),并且还需要做很多额外的工作才能找出正确的做什么为了一个错误。 So instead of writing 所以不要写作

DLLIST *list;
if ((list = dlCreate(tag, object, sz) == NULL) {
  // handle out of memeory error
  ...

the user has to say 用户不得不说

DLList *list = NULL // must initializein this case
if (dlCreate(tag, object, sz, &list) == ERROR || list == NULL)  {
  if (list != NULL) {
    free(list);
  }
  // and other error handling as above

That said, it is often good practice to use a consistent style for a library. 也就是说,对库使用一致的样式通常是一种好习惯。

Answering Question 2: This is sloppy programming, and you are right; 回答问题2:这是一个草率的编程,你是对的; a good programmer should never trust the caller to "do the right thing". 一个优秀的程序员永远不应该相信打电话者“做正确的事”。 Always add code to validate arguments. 始终添加代码以验证参数。 If you passed a bad pointer here, you would get a crash! 如果你在这里传递了一个坏指针,你就会崩溃!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM