How Do I Make This Hard to Misuse?
How Do I Make This Hard to Misuse?
Posted Apr 1, 2008 5:41 UTC (Tue) by jzbiciak (guest, #5246)Parent article: How Do I Make This Hard to Misuse?
One of my favorite C APIs to love to hate: fputc(int, FILE *) and fgets(char *, int, FILE *)
Why is the file pointer the last argument?! As Rusty pointed out, "context" arguments such as handles (FILE * in this case) idiomatically belong at the front, just like in fprintf(FILE *, const char *, ...).
      Posted Apr 1, 2008 10:21 UTC (Tue)
                               by xbobx (subscriber, #51363)
                              [Link] (12 responses)
       
     
    
      Posted Apr 1, 2008 11:14 UTC (Tue)
                               by jzbiciak (guest, #5246)
                              [Link] (11 responses)
       Sounds plausible (and quaint!), but I'm not sure I'm following the scenario.  I'm interpreting "reverse order" as "left most argument pushed last."  So, suppose putchar(int c) is a wrapper around fputc(int c, FILE *f): 
So, in pseudo-code, the resultant assembly ought to look roughly like this (assuming tail-call optimization):
 It seems like if arguments went in reverse order, then adding the FILE * argument at the beginning would be the optimization: I must be missing something. 
     
    
      Posted Apr 1, 2008 17:22 UTC (Tue)
                               by vmole (guest, #111)
                              [Link] (7 responses)
       My guess is that it's even simpler than that: we've got a function that puts a character to stdout, and we need one that puts to an arbitrary FILE *, so we'll add an argument.
 Many years ago, I worked on a Data General. The COPY and MOVE commands took, as a first argument, the destination file or directory. This made some sort of sense from an implementation point of view, but, as the TA warned at the time, "Sooner or later *all* of you are going to overwrite a source file." I think we all did.
      
           
     
    
      Posted Apr 7, 2008 7:49 UTC (Mon)
                               by liljencrantz (guest, #28458)
                              [Link] (6 responses)
       
     
    
      Posted Apr 7, 2008 14:38 UTC (Mon)
                               by nix (subscriber, #2304)
                              [Link] (5 responses)
       
     
    
      Posted Apr 7, 2008 17:17 UTC (Mon)
                               by vmole (guest, #111)
                              [Link] (4 responses)
       It's consistent, kinda. The problem comes when describing: "cp foo bar" translates as "copy _foo_ to _bar_" okay, but the obvious translation of "ln foo bar" to "link _foo_ to _bar_" doesn't; the latter seems to say _bar_ is the original, at least to my taste. You have to process it as "create a link to _foo_ named _bar_". Or just memorize it. :-)
      
           
     
    
      Posted Apr 8, 2008 8:24 UTC (Tue)
                               by IkeTo (subscriber, #2122)
                              [Link] (3 responses)
       
     
    
      Posted Apr 8, 2008 12:13 UTC (Tue)
                               by jzbiciak (guest, #5246)
                              [Link] (2 responses)
       
     
    
      Posted Apr 9, 2008 3:06 UTC (Wed)
                               by roelofs (guest, #2599)
                              [Link] (1 responses)
       
 
...are equivalent.  Sadly, cp foo/bar doesn't quite work.
 
Greg
      
           
     
    
      Posted Apr 9, 2008 3:33 UTC (Wed)
                               by jzbiciak (guest, #5246)
                              [Link] 
       
     
      Posted Apr 1, 2008 18:00 UTC (Tue)
                               by felixfix (subscriber, #242)
                              [Link] 
       
     
      Posted Apr 2, 2008 4:27 UTC (Wed)
                               by xbobx (subscriber, #51363)
                              [Link] (1 responses)
       
     
    
      Posted Apr 2, 2008 4:36 UTC (Wed)
                               by jzbiciak (guest, #5246)
                              [Link] 
       
     
    How Do I Make This Hard to Misuse? 
      
> Why is the file pointer the last argument?!
That would probably be an optimization.  C function calling convention is that arguments are
pushed onto the stack in reverse order, so with this function the FILE pointer is pushed
first.  Then the caller is free to manipulate the stack without touching the FILE pointer, and
possibly call these functions multiple times.  Otherwise, code that repeatedly gets input
from/outputs to stdout (e.g., *everything*, at least when libc was designed) has to push/pop
the stdout FILE ptr around _every_ call to these functions.
Not that this would ever be noticeable on modern hardware, just saying...
How Do I Make This Hard to Misuse? 
      
int putchar(int c)
{
    return fputc(c, stdout);
}
    POP 'c' into a register
    PUSH 'stdout'
    PUSH 'c' back on stack
    JUMP to fputc and let it return for us.
    PUSH 'stdout'
    JUMP to fputc and let it return for us
How Do I Make This Hard to Misuse? 
      How Do I Make This Hard to Misuse? 
      
Kind of how the ln command in unix works, then? I've always found this massively unintuitive.
How Do I Make This Hard to Misuse? 
      
So do I, but I think that's because of overexposure to C, where that sort 
of thing is helpfully always the other way around.
If you think about it, ln(1) is perfectly consistent with cp(1): it 
creates or updates (for directories) the last thing you list.
How Do I Make This Hard to Misuse? 
      How Do I Make This Hard to Misuse? 
      
> but the obvious translation of "ln foo bar" to "link _foo_ to _bar_" doesn't
I see this problem as an inaccuracy of the translation "link _foo_ to _bar_".  This seems to
imply that both _foo_ and _bar_ are pre-existing, and somehow a "link" is created between them
as a result of running the command.  Obviously not what is done by "ln".  It is instead to
"build a link to _foo_ called _bar_".  The cp is to "make a copy of _foo_ called _bar_".
Pretty consistent to me.
How Do I Make This Hard to Misuse? 
      
Although, since the final argument can be a directory, perhaps the best connector for both is
"at":
Make a copy of foo _at_ bar
Make a link to foo _at_ bar
Or in the plural case:
Make copies of foo, bar, baz, quux _at_ dest
Make links to  foo, bar, baz, quux _at_ dest
      Also keep in mind that the target for ln is optional.  Thus:
How Do I Make This Hard to Misuse? 
         ln -s foo/bar .
   ln -s foo/bar
How Do I Make This Hard to Misuse? 
      
That aspect of 'cp' always drove me nuts, probably because I learned MS-DOS first.  I've found
myself tempted to write a wrapper around 'cp' to make that form work.
I won't, though, only because I know it'll wreak havoc when I go to use someone else's account
for whatever reason.  (e.g. to show them how to do something.)
How Do I Make This Hard to Misuse? 
      
Possibly push the file handle, push the char, call, replace the char at top of stack, call,
repeat.  But I am just guessing, and hate working on code like that.  I spent two years
dealing with the memory constraints that made such code tempting, and despised it.
      True, for that specific case it is better the other way around.
But suppose you have a function such as:
How Do I Make This Hard to Misuse? 
      
void print_strings(FILE *stream, int num_strings, const char **list) {
    int i;
    for (i = 0; i < num_strings; i++) {
        fputs(list[i], stream);
    }
}
In this case, the assembly for this function will push stream once, then just push/pop n pointers to strings onto the stack and call fputs to print all of the strings.  One could imagine that this would be useful when, say, implementing fprintf or other similar higher-level functions which all output to the same FILE *.
      
          How Do I Make This Hard to Misuse? 
      
See?  I knew I was missing something.  Thanks to you and felixfix both, since you are both
describing the same particular optimization.  Cute, in an ugly, quaint way.  :-D
I'm certain I'm guilty of far worse horrors in my 2 decades of assembly programming.  This
optimization never occurred to me since I've always managed to pass arguments in registers.
(An odd fluke of history, that.  I've written whole-program assembly where I control all the
conventions, C with inline-asm only (no function calls from asm) on machines with stack-based
calling conventions, and C callable asm on machines with register based calling conventions.)
 
           