In "Advanced Programming in the UNIX Environment", W. Richard Stevens says it is a performance optimization:
By specifying the highest descriptor we're interested in, the kernel can avoid going through hundred of unused bits in the three descriptor sets, looking for bits that are turned on.
(1st edition, page 399)
If you are doing any kind of UNIX systems programming, the APUE book is highly recommended.
fd_set is usually able to track up to 1024 file descriptors.
The most efficient way to track which
fds are set to
0 and which are set to
1 would be a bitset, so each
fd_set would consist of 1024 bits.
On a 32-bit system, a long int (or "word") is 32 bits, so that means each
1024 / 32 = 32 words.
nfds is something small, such as 8 or 16, which it would be in many applications, it only needs to look inside the 1st word, which should clearly be faster than looking inside all 32.
/usr/include/sys/select.h for the values on your platform.)
As to why the function signature isn't
int select(fd_set *readfds, int nreadfds,
fd_set *writefds, int nwritefds,
fd_set *exceptfds, int nexceptfds,
struct timeval *timeout);
My guess is it's because the code tries to keep all the arguments in registers, so the CPU can work on them faster, and if it had to track an extra 2 variables, the CPU might not have enough registers.
So in other words,
select is exposing an implementation detail so that it can be faster.