From the man pages, I found that size_t has the range of 0 to SIZE_MAX, and ssize_t has the range of -1 to SSIZE_MAX. So, after printing those values on a 64bit system, I have the following results:

ssize_t max: 9223372036854775807
size_t max: 18446744073709551615
int main() {
  std::println("ssize_t max: {}\nsize_t max: {}", SSIZE_MAX, SIZE_MAX);
}

Is there a reason for using a signed integer to allow the storage of negative values, instead of creating a macro like this (as an example for 64-bit machines):

#define OPERATION_ERROR 0xFFFFFFFFFFFFFFFF

and continue using unsigned integer, so you have 2 times the range?

22 Replies 22

It could be consdered an ugly design, but the negative values are used to specify error codes.

For example, you can write a function that reads data from a network service, return ssize_t from 0 or more to provide the information on the actual number of bytes read, and some negative error code to report connection failure or other network failure conditions.

You may like it or not, but this is what it is, designed for a quite simple mechaism.

The first s in ssize_t stands for signed, hence "signed size type". The std::size_t is part of the standard C++ library. While ssize_t is part of POSIX, and used by some of the POSIX APIs.

If ssize_t were unsigned, it would be identical to size_t and therefore lose its unique utility. That utility, as Sergey pointed out above, is to allow for an easily categorized suite of error states. An alternative design to ssize_t would not in my mind be a single unsigned integer, but perhaps a rich union which forces consumers to read a "success" value and interpret the stored data accordingly, similar to Rust's Result type.

@sergey-a-kryukov but it can only store values from -1 to SSIZE_MAX, it seems, that it doesn't seem to make sense, as the only negative value you can have is -1

what is "the man pages" ? Where did you read that the only negative value it can have is -1 ? Thats not correct

The existence of methods like read (glibc) which return values from -1 to SSIZE_MAX does not indicate that "[the ssize_t type] can only store values from -1 to SSIZE_MAX". It remains that the type may potentially encode multiple distinct error states, and this paradigm is used successfully in C/C++ code out in the wild today. I would also say that 2 ** 64 - 1 and 2 ** 63 - 1 are both functionally infinite for the typical use cases of ssize_t.

@463035818-is-not-an-ai here is the link https://manpages.opensuse.org/Tumbleweed/man-pages/ssize_t.3type.en.html

Re “… ssize_t has the range of -1 to SSIZE_MAX”: That is not what the man page says. It says the range is at least [-1, SSIZE_MAX]. That means an implementation that wanted to use additional negative values for error codes could extend the Linux requirements to support a greater range. If the man page at said the range were [-1, SSIZE_MAX], not at least that range, then an extension would not be conforming.

The difference between "capable of storing values at least in the range [-1, SSIZE_MAX]" (from the manpage you sent) and "it can only store values from -1 to SSIZE_MAX" is appreciable.

@eric-postpischil thanks, it makes sense now, although can you please tell me which function(s) use such a method of error handling, from what I've seen most of the linux apis return -1 in the case of error and set errno to the specific error code

POSIX APIs generally use -1 and set errno. pthread does not use errno and instead returns the error codes directly. However, the Linux kernel API generally uses negative return values to return negated error codes, with non-negative values being successful return values. Overall, that is the most efficient use of the return value, resulting in the most performant code.

It also avoids the usual downsides and gotchas of errno such as cleanup functions in the error return path resetting the error code before it is delivered to the caller.

ssize_t is NOT for error codes it is to report sizes of datastructures. And it's signed property can be useful when calculating offsets and being able to compare them to <0 . For errors use std::expected or exceptions

@Pepijn Kramer off_t/off64_t may technically be more appropriate for offsets. I do agree that the signedness of ssize_t is useful for arithmetic.

If the question is tagged C and C++, it is better to use an example which is valid in both languages. POSIX should be tagged when the question is about POSIX definitions (ssize_t). Anyway, POSIX.1-2024 defines ssize_t with this purpose:

Used for a count of bytes or an error indication.

With the mentioned range requirement:

The type ssize_t shall be capable of storing values at least in the range [-1, {SSIZE_MAX}].

where the value -1 obviously is needed as the error indication.

Notably, using unexplained magic numbers like -1 for error detection, as well as mixing up error codes with data in the same storage, is considered very bad programming practice. So the actual question we should be asking is why POSIX is inventing poorly-considered types that encourage bad program design?

PoSIX considered harmful.

@Lundin I don't think it's fair to blame POSIX for the way errors were returned in original Unix implementations - for a committee-driven standardization of multiple pre-existing implementations, POSIX could be a LOT worse*. IMO "-1 for failure, 0 for success or a non-negative number for a count of bytes" isn't unreasonable for a general return status across multiple functions, and "magic numbers" are only "magic" when they're not well-understood. Nevermind that there are numerous standard C function that are also specified to return -1 on an error, so it's not really a problem limited to POSIX.

Something like ssize_t is needed to make functions such as read() and write() 64-bit compliant while retaining compatibility with legacy code. The specified range of ssize_t - from -1 to SSIZE_MAX - seems to me to be the least-restrictive way to specify ssize_t in a way that both supports 64-bit implementations while retaining compatibility with existing code, as long as any implementation uses a large-enough value of SSIZE_MAX - although it wouldn't surprise me if Microsoft doesn't...

1978's int read( int, char *, int ) had to change without breaking everything extant.

* - imagine systemd getting standardized under POSIX 😱😱😱😱

Asking for Advice: How do I block all "Advice" tagged "questions"?

Although the C standard does not define ssize_t, it acknowledges the existence of a signed integer type corresponding to size_t, for example in the description of the z length modifier for the formatted input/output functions.

@Andrew Henle, numerous standard C function that are also specified to return -1 on an error --> Off hand, I can think of 2 sets: time() & friends and various wc...(). The are many I/O functions that return EOF, yet that is not specified as -1 - just some negative. Posix I/O functions could have done likewise.

"why signed integer is used for the ssize_t" --> if it were unsigned, no need for a new type, just use size_t. The issue is the function is designed to return 2 pieces of info: the length (nominally in the range of the unsigned type size_t) and an error indication.

The compromise is to use a signed type, with about the positive range of size_t and -1 to indicate an error. As ssize_t is often the same bit-size as size_t, this implies the upper half the range of size_t is problematic for the size_t input parameter. At the time of this decision, such large size_t values were beyond reason and so loss of the upper half of of size_t was not a concern. With a signed type, a simply < 0 compare suffices for error detection. Combining a value and error code in the same type is less in fashion than before yet still common practice.

IMO, a cleaner function interface would somehow return length and error info in separate values.

I'd like to emphasize the point of @Xavier Pedraza : Signed integers will be promoted to unsigned types if they have no bigger bit size. This can easily lead to errors.

Consider:

if (7 - std::size(vec) < 0)

this will always evaluate to false, as the integer is promoted to unsigned. Using ssize_t will fix it.

@sat0sh1c — I started my comment with the note that this design can be considered ugly. This is not my idea, and I cannot say I like it much. This is just what it is.

But I don't think the valid values start with -1. Different negative values can be used to conduct different error conditions.

Your Reply

By clicking “Post Your Reply”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.