cdocumentationdoxygen

In C does it make sense to use Doxygen's [out] directional attribute alone?


/**
 * @param[in,out] param Pointer to the destination where to save the value.
 */
void foo(int *param) {
    if(param != NULL) {
        *param= 100;
    }
}

/**
 * @param[in,out] param Pointer to the destination where to save the value.
 * @retval true Indicates that the value has been successfully saved in \p param.
 * @retval false Indicates that the value in \p param could not be saved 
 * because it points to \c NULL.
 */
bool bar(int *param) {
    if(param != NULL) {
        *param= 100;
        return true;
    }
    return false;
}

I used the [in,out] attribute because before changing the destination pointed to by *param I read the address contained in param to check that it was different from NULL.

So the behavior of foo depends on the value of param for this reason I used the in attribute, also the value pointed to by *param is changed so the out attribute must also be used.

More generally in C, when you pass a pointer to a function it is normal to check that the address is valid (!= NULL), in other words you always end up "reading" the parameter forcing you to use the in attribute even when the parameter is only used to return a value.

I can't think of an example in C where it makes sense to just use the [out] attribute.

What am I doing wrong in my reasoning?

EDIT

Adding an example closer to the doubt I'm facing in my project.

In my project I have functions like this, and with mutexFree2() I can't decide whether to use [in] or [in,out], the goal of the latter is not only to destroy the mutex but also to clean the pointer, so in a way the handle parameter is also used to return something back.

I really can't make up my mind!

typedef void *mutex_h; // Define mutex handle

/**
 * @param[in] handle No doubt, it is an input parameter only.
 * @retval true Mutex destroyed.
 * @retval false Mutex destruction failed.
 */
bool mutexFree(mutex_h handle) {
    // free the mutex
}

/**
 * @param[in,out] handle If I only used the [in] attribute it would be like 
 * saying that this function is the same as mutexFree(), which it is not 
 * because the goal is also to clear the pointer.
 * @retval true Mutex destroyed.
 * @retval false Mutex destruction failed.
 */
bool mutexFree2(mutex_h *handle) {
    // free the mutex
    *handle = NULL;

}

Solution

  • It depends on what exactly you want to document, specifically in case of pointers.

    From a technical point of view you could say that all parameters are [in] because C only has call-by-value, so you cannot pass a modified pointer value to the caller.

    Or from a higher level application point of view, param in bar is [out] because you don't read the value pointed to by param, while the checking for NULL can be considered as technical error handling and not relevant for the documentation.

    In my opinion, your approach of using [in,out] mixes two things:

    Technically, it is always necessary to read a pointer unless the pointer parameter is unused. So interpreting [in] as referring to reading the pointer value does not add valueable information because it is self-evident.

    Of course you can have a different opinion in case the pointer value is read for something else than accessing the value it points to.

    You could as well have 3 different things if the function would additionally read the value of the number pointed to by param before modifying it. Then it depends on your interpretation if [in] refers to reading the pointer itself or reading the pointed-to value.

    Example:

    /**
     * @param[in,out] param Pointer to the value to be modified.
     * @retval true Value modified.
     * @retval false Invalid pointer value in \p param.
     */
    bool baz(int *param) {
        if(param != NULL) {
            *param += 100;
            return true;
        }
        return false;
    }
    

    This function reads the pointer param for checking, and it reads and writes the number.

    I usually consider the purpose of the function. Is the main focus reading or writing the pointed-to value or is it about checking the pointer?

    In this case I would consider the NULL pointer check as less important.


    Regarding your function mutexFree2, I clearly consider it as [out] because the (pointer) value pointed to by handle is modified. Depending on what // free the mutex does, you have to decide if it is also [in] or not. This decision can be opinion-based.

    In contrast to function bar, this function does not read the (void **) pointer value for other purposes than accessing the pointed-to value, which is a void *, so it does not have the ambiguity about what [in] refers to.