About one trick to return an error code from a function

    The Linux kernel is a storehouse of both applied algorithms and some hacker or half-hacker tricks designed to speed up and / or reduce the size in memory (memory footprint). I want to talk about one of these half-hacker tricks further.


    In our wretched world , the C language as such does not exist references and classes, but there are pointers and structures. The question often arises, what to choose as the return value of a function that should return a pointer to a living object to the caller? There are at least two prototype options:

    struct obj *get_obj(...);
    

    and
    void get_obj(..., struct obj**);
    


    Why did I highlight the second method, although it seems ineffective here? And here's the thing. Initially, we can return a pointer to an object or NULL if one was not found. However, any programmer has a question: what about the exceptions inside the method? Yes, yes, in the Wild West in the C language we use the return code. And here there is such a picture (see prototypes above and compare):
    struct obj *get_obj(..., int *ret);
    int get_obj(..., struct obj **);
    


    But now the task is to reduce the number of function arguments, since this greatly affects both the execution speed and the amount of memory consumed on the stack (we are talking about the OS kernel, right? :-)).

    For this, such a solution was invented. Since void * we still have a number, let's divide the possible values ‚Äč‚Äčinto three segments (consider the 32-bit case):
    • 0x00 ... 0x10
    • 0x11 ... 0xffffffff - MAX_ERRNO
    • 0xffffffff - MAX_ERRNO + 1 ... 0xffffffff

    The first is a NULL pointer, the second is a regular address space, and the third is an invalid pointer or an error code. Accordingly, several macros appeared for verification:
    ZERO_OR_NULL_PTR();
    IS_ERR();
    IS_ERR_OR_NULL();
    

    And people started using them!

    The resulting prototype has turned into:
    struct obj *get_obj(...);
    

    And an example of use:
    struct obj *get_obj(...)
    {
      struct obj *obj = malloc(sizeof(*obj));
      int ret;
      if (!obj)
        return ERR_PTR(-ENOMEM);
      ret = do_smth(obj, ...);
      if (ret) {
       free(obj);
       return ERR_PTR(ret);
      }
      return obj;
    }
    int whatever(...)
    {
      struct obj *obj = get_obj(...);
      if (IS_ERR(obj))
        return PTR_ERR(obj);
      printf("Cool object %p\n", obj);
      return 0;
    }
    

    However, there are problems with this approach, for example, due to the incorrect use of the IS_ERR_OR_NULL macro for those parts of the code where NULL is a valid return, for example, when the object is responsible for some functionality in the kernel that is turned off in this kernel assembly. Then a NULL pointer is returned, which must be handled differently!

    Also popular now: