
What If I Don't Actually Like My Users? (2008) - JoshTriplett
http://ozlabs.org/~rusty/index.cgi/tech/2008-04-01.html
======
stcredzero
I once had to use a C API where you had to guess the size of of the buffer of
data you were going to get back, and keep guessing until your number was
compatible with the amount of data. So I did what any good CS person would do,
and started with a small, reasonable number, then doubled it until the API was
happy. Was this documented? No. Everything _but_ how you were supposed to
specify the buffer size was documented. However, this code worked in
production, and AFAIK, it's still running in production to this day.

~~~
xyzzy123
Lots of win32 API calls work this way ><

~~~
roel_v
Like which one? Pretty much every single one I know, you pass a buffer and a
pointer to a buffer size; the first time you call with buffer NULL and the
required buffer size is put in the buffer size; then you allocate the buffer
and call again with the proper buffer allocated.

~~~
xyzzy123
QueryDosDevice? You're right that mostly length is passed back, but esp in
libs you get this problem you pass memory you own, some underlying function
fails with "buffer too small", but buffer length was not in/out in the
interface. Places where this can happen are not always obvious from reading
the msdn.

------
lstamour
Note the "Hard to Misuse" list is at
[http://ozlabs.org/~rusty/index.cgi/tech/2008-03-30.html](http://ozlabs.org/~rusty/index.cgi/tech/2008-03-30.html)

------
kazinator
> _Here 's my favorite (now fixed) example, from the glibc snprintf man page:
> _

Not fixed is that the ISO C wide character version of the function (swprintf)
specifies a gratuitously different return value convention from from that of
snprintf.

------
juped
I think git usually sits around a -6 or -7.

------
B1FF_PSUVM
> because I know I'm not as smart as I think I am.

They should print that into every mirror manufactured.

~~~
metaphor
Reminds me of a comment included in code that I wrote a couple weeks back:

    
    
      // ... Function strcmp() returns 0 on match, therefore, !strcmp() is TRUE on match.
    

I deliberated with my inner voice for a minute on whether to include a comment
or not. Ended up including the above thinking 18 _!strcmp()_ statements looked
strangely illogical in context even after just writing it.

~~~
MaulingMonkey
I always go with strcmp(...) == 0 instead of !strcmp(...).

This better reinforces my mental model of it (like many comparison functions)
having 3 possible return values: <0, =0, or >0\. !strcmp(...) may be terser,
but it _is_ illogical in that logical not isn't an operator. I dig using it on
booleans, or "booleanish" values (null vs non-null pointers), but this
conceptual tri-state isn't even "booleanish". But C didn't have a proper bool
type for awhile, and C++ inherited it's baggage, so they'll compile it.

Meanwhile C# doesn't even think my "booleanish" tests are logical enough to
allow to compile - no null reference tests with "operator!" for me.

------
hcs
> -10. It's impossible to get right.

char * gets(char * s) ?

~~~
JoshTriplett
Absolutely. Also any interface in C or POSIX specified to return a pointer to
local static memory, such as "char * basename(char * path)" (and dirname), or
"char * ptsname(int fd)", or "char * asctime(const struct tm * tm);".

~~~
Someone
There's also the _write_ call that succeeds as soon as it can write a single
byte, regardless of the byte count passed in.

For example,
[https://en.m.wikipedia.org/wiki/Write_(system_call)#POSIX_us...](https://en.m.wikipedia.org/wiki/Write_\(system_call\)#POSIX_usage)
warns _" The write function returns the number of bytes successfully written
into the array, which may at times be less than the specified nbytes"_

Yet, the example following it
([https://en.m.wikipedia.org/wiki/Write_(system_call)#Usage_Ex...](https://en.m.wikipedia.org/wiki/Write_\(system_call\)#Usage_Example))
doesn't handle that case or error codes such as EINTR.

I remember reading a text describing how to properly use that call, but cannot
find it anymore. Faulty examples like the one above are easy to find, though.
For example, [http://stackoverflow.com/questions/14417806/are-posix-
read-a...](http://stackoverflow.com/questions/14417806/are-posix-read-and-
write-system-calls-atomic) and
[http://stackoverflow.com/questions/10650861/atomicity-of-
wri...](http://stackoverflow.com/questions/10650861/atomicity-of-write2-to-a-
local-filesystem) are good questions, but no answer points out that, even if
write were atomic, it would not do you any good, as you may have to call it
multiple times.

It probably is not that difficult, but I think it would take me a day to wrap
this function in something that doesn't explode in your face, and be confident
that I did it correctly, and even then, I would wake up in bed wondering
whether I handled all edge cases correctly.

~~~
ArkyBeagle
I can't say this is true in all cases, but I believe that
fopen()/fread()/frwrite()/fclose() all cover this for you. You can get the
file number for an opened FILE * block thru something like fileno() or
filenum() or something.

The f _() calls all buffer for you.

The problem is that write() is entangled in the ioctl() universe in many
implementations of 'C' and that gets funky fast.

_Beyond* that, reading and writing may involve making your own furniture to
hide the ugly from dealing with peripherals.

~~~
JoshTriplett
You can also use fdopen to get a FILE* from a file descriptor, and then use
the buffering and read/write logic of the C library, rather than having to
write your own write_in_full function.

------
pdkl95
> -8. The compiler will warn if you get it right.

> bind() socket library call ... takes a struct sockaddr [when you are usually
> using a struct sockaddr_in]

Except the compiler is correct. If you want to use a polymorphic type in a
generic call that requires the parent type, you should include a cast.

From the example in my man page for bind(2):

    
    
        int main(int argc, char *argv[])
        {
            int sfd;
            struct sockaddr_un my_addr;
        
            /* ... */
              
            if (bind(sfd, (struct sockaddr *) &my_addr,
                       sizeof(struct sockaddr_un)) == -1)
                handle_error("bind");
    
            /* ,,, */

~~~
sjolsen
I don't think the complaint is that the compiler won't accept a struct
sockaddr_in* where the API expects a struct sockaddr* . I think the complaint
is that the API is designed in such a way that obtaining the required struct
sockaddr* involves casting between incompatible types. There are more robust
ways of doing type erasure, even in C.

------
userbinator
> I've been coding in C for about 20 years, and about five years ago I spent
> an hour chasing a case where I'd done if (strcmp(arg, "foo")) instead of if
> (!strcmp(arg, "foo")).

This felt completely intuitive to me the first time I saw it: a comparison of
characters can result in "less", "greater", or "equal" much like a subtraction
would, and if this is expressed as an integer, < 0, > 0, and == 0 are a very
sensible set of possible alternatives, and of those the only one that makes
most sense as "equality" is the == 0, which is idiomatically expressed as !. I
think if(strcmp(...)), if(!strcmp(...)), and ditto for memcmp() and related
"3-value" comparison idioms might be one of those things that divide
programmers into two groups: one that instantly sees the elegance of this
design and thinks it's so natural and obvious, and one that gets eternally
confused by it.

~~~
PhasmaFelis
Elegantly solving the wrong problem is not elegant, though.

strcmp is a comparison. Comparisons are intuitively boolean unless there's a
clear reason to be otherwise, and "false=0, true=nonzero" is well-known.
Therefore, "strcmp('foo', 'foo')" shouldn't return 0 any more than "5 == 5"
should return 0.

If the function were called "strsub" or "strdiff" and was described as taking
the difference of two strings, then this might be an elegant solution, but
"comparison" means something specific in a programming context, and this isn't
it.

~~~
rabidrat
In an assembly context, cmp means 'set the flags as though you subtracted but
don't do the subtraction'. This particular misdesign is from before C.

~~~
ArkyBeagle
I blame Ben Franklin for guessing wrong about the sign of the charge of the
electron.

(I keed, I keed )

------
busterarm
I tallied about a -11 using the Youtube Player API today.

