clanguage-lawyerc99

Can code use size_t instead of ptrdiff_t when subtracting two pointers where the minuend >= subtrahend and the result is guaranteed to be < SIZE_MAX?


size_t array_len = SIZE_MAX;
int *my_array = calloc(array_len, sizeof(int));

int compar(const void *n_to_search, const void *arr_element)
{
    if(*(int *)n_to_search > *(int *)arr_element)
        return 1;
    else if(*(int *)n_to_search < *(int *)arr_element)
        return -1;
    else
        return 0;
}

void search(int n_to_search, size_t *idx)
{
    int *result = bsearch(&n_to_search, my_array, array_len, sizeof(int), compar);
    
    if(result != NULL)
        // Is this operation valid according to c99 standard?
        *idx = result - my_array; 
}

In this case result is guaranteed to be greater than or equal to my_array, so the result of the subtraction will always be >=0, furthermore it is guaranteed that result - my_array < SIZE_MAX since bsearch() takes array_len as parameter, so the search will only happen between the first array_len elements of my_array.

So my question is: can code save the result of the subtraction in a variable of type size_t without ending up in an UB according to the c99 standard?


Solution

  • Can code use size_t instead of ptrdiff_t when subtracting two pointers where the minuend >= subtrahend and the result is guaranteed to be < SIZE_MAX?

    Yes when PTRDIFF_MAX >= SIZE_MAX or the result is guaranteed to be <= PTRDIFF_MAX, else code risks UB.


    In the case of a large allocations, we can have issues:

    We are then left with the subtraction result - my_array which returns a type of ptrdiff_t, "which is the signed integer type of the result of subtracting two pointers;" (C23dr § 7.21 3).

    ptrdiff_t wider than size_t:
    When PTRDIFF_MAX >= SIZE_MAX, there is no issue in converting the signed difference of result - my_array to an size_t. The difference is always 0 or positive.

    ptrdiff_t same width as size_t:
    When PTRDIFF_MAX < SIZE_MAX, (e.g. PTRDIFF_MAX == SIZE_MAX/2) we risk undefined_behavior (UB) due to:

    When two pointers are subtracted, both shall point to elements of the same array object, or one past the last element of the array object; the result is the difference of the subscripts of the two array elements. The size of the result is implementation-defined, and its type (a signed integer type) is ptrdiff_t defined in the <stddef.h> header. If the result is not representable in an object of that type, the behavior is undefined. C23dr § 6.5.7 10

    J.2 Undefined behavior
    The result of subtracting two pointers is not representable in an object of type ptrdiff_t (C23dr J.2. 6.5.7).

    The UB may or may not be tolerable (e.g. the subtraction "wrapped" in which case the following conversion to size_t wraps to the desired index) - yet it is still UB.


    Code attempting calloc(a, b) and subsequent code for that pointer where a*b exceeds minimum(SIZE_MAX, PTRDIFF_MAX) risks portability issues as here be dragons.