Comparing size_t to -1

1k Views Asked by At

Note: it has been suggested that this question duplicates Can I compare int with size_t directly in C?, but the question here specifically asks about comparing size_t with a negative value. For this reason, it should be re-opened. (And besides, it has generated a lot of thoughtful discussion!)

I'm staring at some library code (I am looking at you, Microchip), declared to return a size_t:

size_t SYS_FS_FileWrite(SYS_FS_HANDLE handle, const void *buf, size_t nbytes);

That is documented as returning -1 on an error. But size_t is an unsigned value. So, in theory and/or in practice, is the following allowed?

if (SYS_FS_FileWrite(handle, buf, nbytes) == -1) {
    report_errror();
}
4

There are 4 best solutions below

8
Eric Postpischil On BEST ANSWER

It is impossible for a routine that returns a size_t to return −1, but it is perfectly fine for it to return the result of converting −1 to size_t (presuming the relevant environment does not need that value for any conflicting purpose). If the documentation states the latter, it is fine. If it states the former, the documentation is sloppily written and probably means the latter.

In a comparison of some size_t value x to -1, as in x == -1, the value −1 will be converted to the type size_t if the rank of size_t equals or exceeds the rank of int. This is the case in most C implementations and would be expected in an implementation that uses −1 converted to size_t as a return value. In a C implementation in which size_t had lower rank than int, x could be converted to int (depending on some specifics of the types). That would not change the value, and x == -1 would always evaluate as false.

Per a request in the comments for a test of whether SYS_FS_FileWrite(handle, buf, nbytes) == -1 is a safe test for SYS_FS_FileWrite returning −1 converted to size_t in light of the fact that the usual arithmetic conversions might not produce the desired results, a suitable test is _Static_assert((size_t) -1 == -1, "size_t type is too narrow.");. Also, the test could be written as SYS_FS_FileWrite(handle, buf, nbytes) == (size_t) -1.

1
Andreas Wenzel On

Yes, it is allowed and it will work as intended.

When comparing the integer constant -1 (which has type int) with a value of type size_t, the integer constant -1 will be implicitly converted to size_t. Assuming that size_t has a width of 64 bits, it will be converted to the value 0xFFFFFFFFFFFFFFFF. The return value of the function SYS_FS_FileWrite will also have the value 0xFFFFFFFFFFFFFFFF. Therefore, the comparison will work as desired.

As pointed out in one of the other answers, the ISO C standard does allow a compiler implementation to define the data type size_t in such a way that its rank is smaller than int. In the hypothetical case of this actually happening, the statements in my previous paragraph would not apply. The integer constant -1 would retain its value and would not be converted to a positive value, so that the comparision would always evaluate to false. However, this is only a theoretical concern. In practice, you can rely on size_t having a rank that is equal to or higher than int.

It does not make much sense that the documentation states that the function will return -1. It would make more sense for the documentation to state that it will return -1 converted to a size_t, i.e. that it will return the value (size_t)-1.

3
Vlad from Moscow On

In the expression of the if statement

if (SYS_FS_FileWrite(handle, buf, nbytes) == -1) {

there are used two operands of different types. The left operand of the equality operator has the type size_t and the right operand is the integer constant -1 of the type int.

The compiler needs to determine the common type of the operands.

In this case there are used the so-called usual arithmetic conversions.

As the rank of the type size_t is not less than the rank of the type int (the typs size_t is usually an alias for the type unsigned long and in more rare cases for type unsigned long long) then the operand of the type int is converted to the type size_t.

So under the hood you have in fact

if (SYS_FS_FileWrite(handle, buf, nbytes) == ( size_t )-1) {

the same way as the returned integer constant -1 from the function is converted to the type size_t according to the function return type

size_t SYS_FS_FileWrite(SYS_FS_HANDLE handle, const void *buf, size_t nbytes)
{
    //...
    return ( size_t ) -1;
    //...
}

Thus the if statement may be interpreted like

if ( ( size_t )-1 == ( size_t )-1) {

provided that the function returns -1. Otherwise the left operand of the equation has some other value returned by the function and the expression evaluates to logical false (integer value 0)

From the C Standard (6.5.9 Equality operators)

4 If both of the operands have arithmetic type, the usual arithmetic conversions are performed. Values of complex types are equal if and only if both their real parts are equal and also their imaginary parts are equal. Any two values of arithmetic types from different type domains are equal if and only if the results of their conversions to the (complex) result type determined by the usual arithmetic conversions are equal.

0
chux - Reinstate Monica On

...documented as returning -1

With standard size_t, impossible for a function returning size_t to return -1. Such a function with return -1; instead returns (size_t) -1 or SIZE_MAX.

... in theory and/or in practice, is the following allowed?

On select implementations where SIZE_MAX <= INT_MAX is true, the following is never true, since size_t converts to an int before the compassion and all size are non-negative.

// Weak code
size_t SYS_FS_FileWrite(SYS_FS_HANDLE handle, const void *buf, size_t nbytes);
if (SYS_FS_FileWrite(handle, buf, nbytes) == -1) {
  report_errror();
}

Commonly SIZE_MAX < INT_MAX is false though, so the -1 converts to a (size_t) -1 or SIZE_MAX and the compare is OK.

Better as:

if (SYS_FS_FileWrite(handle, buf, nbytes) == SIZE_MAX) {
  report_errror();
}

On review, this answer too close to other answers, so many it wiki.