Comparing size_t to -1 [duplicate]
Asked Answered
S

4

12

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();
}
Siegel answered 17/4, 2023 at 13:6 Comment(9)
When asking a question about a statement made in some document, give a URL or a bibliographic citation to that document and quote it, so that other people can examine it.Ani
Assuming SYS_FS_FileWrite() returns (size_t)-1 on error, the safest thing to do would be compare the return value to (size_t)-1 rather than a plain old -1 so that you do not have to worry about the value after conversion to a common type by the usual arithmetic conversions.Zebulon
Converting -1 to an unsigned integer type is a common and portable way to get the maximal value of that type.Eddy
@Eddy Converting -1 to an unsigned integer type is a common and portable way ... Not if sizeof(size_t) < sizeof(int), then -1 will not be converted at all. if (UINT8_MAX==-1) isn't going to be true on all that many implementations...Forme
@fearless_fool, "documented as returning -1 on an error" is poor documentation and simply wrong.Henebry
@AndrewHenle If it is not converted, then my comment does not apply since it only mentions the result of a conversion and nothing about when a conversion happens. My purpose in this context was to maybe explain why anyone would do the seemingly strange thing to return -1 when return type is unsigned. Btw., it would be more readable to e.g. #define ERROR_VALUE ((size_t)-1) and use that as return value as well as for checking the returned result.Eddy
@Eddy Perhaps "Explicitly converting" would have been a better way to write it.Zebulon
@IanAbbott That is a different statement. The result does not depend on whether or not the conversion is explicit. Then it could be "Explicitly or implicitly converting...". Anyway, I think the point has been made by now :-).Eddy
My preference would be to #define ERROR_VALUE SIZE_MAX as the error value -- that would avoid any ambiguity or portability issues.Siegel
A
16

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.

Ani answered 17/4, 2023 at 13:10 Comment(8)
Is it possible to formulate a "static_assert" to check if the rank of size_t equals or exceeds the rank of int ?Eddy
@Eddy _Static_assert(SIZE_MAX >= INT_MAX,""); or the like.Henebry
@chux-ReinstateMonica: Suppose size_t is unsigned short with SIZE_MAX = 65,535, and INT_MAX is 32,767. ThenSIZE_MAX >= INT_MAX is true (even given the usual arithmetic conversions), but size_t has lower rank than int. However, the size_t type and the “−1” value used in this case would not cause problems, so merely testing for rank is not necessarily what we want to do. It might be sufficient that SIZE_MAX >= INT_MAX evaluates as true.Ani
@EricPostpischil True that in that case the ranking order is as described, yet IMO, nielsen true question goal, is not as stated check the rank, but to determine, as you say, test for problems, which SIZE_MAX >= INT_MAX should address.Henebry
Thanks both. Yes, my immediate question was to check for problems, but theoretically it is a bit funny that the C Standard states rules in terms of "rank" but does not offer any (obvious) way to compare ranks of two types. The Standard could have defined a rank() keyword the same way as e.g. sizeof(). It would not be necessary to specify the absolute "rank" values, only the order, i.e. rank(type1) <= rank(type2) if and only if type1 is an integer type or of integer type with rank less than or equal to type2.Eddy
@chux-ReinstateMonica: In one of those oh, this is obvious moments, an assertion that will test whether SYS_FS_FileWrite(handle, buf, nbytes) == -1 is a safe test is of course _Static_assert((size_t) -1 == -1, "size_t type is too narrow.");.Ani
@EricPostpischil _Static_assert((size_t) -1 == -1,... is nicely to the point. SIZE_MAX >= INT_MAX is useful in an #if to steer code as needed.Henebry
You can just have a constant #define SYS_FS_FILE_WRITE_ERROR ((size_t)-1) and SYS_FS_FileWrite(handle, buf, nbytes) == SYS_FS_FILE_WRITE_ERROR should work on all implementations without any asserts or ifdefsMalapropos
A
7

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.

Abbyabbye answered 17/4, 2023 at 13:10 Comment(1)
IMO, a clearer way to state it, for the case where size_t has a lower conversion rank than int (e.g. if it's unsigned short), is that size_t gets promoted to int, instead of the -1 (which is implicitly an int) getting promoted + converted to an unsigned type to match size_t to make both sides of == match.Athwart
H
1

...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.

Henebry answered 17/4, 2023 at 13:6 Comment(0)
T
1

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.

Tieratierce answered 17/4, 2023 at 13:17 Comment(3)
"As the rank of the type size_t is not less than the rank of the type int" is commonly true, but not specified by C as true since SIZE_MAX < INT_MAX is possible. In that case, SYS_FS_FileWrite(handle, buf, nbytes) == -1 is never true.Henebry
@chux-ReinstateMonica Truly speaking I do not know where the rank of size_t is less than the rank of int because the C Standard recommends to use unsigned long type as the type size_t or in an exclusive case to use unsigned long long .Tieratierce
I've only once come across a 32-bit embedded machine with small memory that had SIZE_MAX = 0xFFFF and INT_MAX == 0x7FFF_FFFF. Such machines are allowed by C, yet since they cause issues, as suggested with SIZE_MAX < INT_MAX, are relegated to "I don't care if it is possible, I am not writing code to support such unicorns" pile.Henebry

© 2022 - 2024 — McMap. All rights reserved.