Nat! bio photo

Nat!

Senior Mull.

Twitter RSS

Github

va_list is not va_list

This is somewhat mysterious bug I ran into. I call it a bug, though what it does is possibly sensible. Anyway, just from looking at it you will never figure it out. :)

 1 #import <Foundation/Foundation.h>
 2 
 3 //
 4 // just a small struct to carry parser context around
 5 // pared down to va_list. (You need to use va_list *)
 6 //
 7 typedef struct
 8 {
 9    va_list    *args;
10 } parse_context;
11 
12 
13 //
14 // this functions sets the parse_context up
15 //
16 static void   init_parse_context( parse_context *p,
17                                   va_list *args)
18 {
19    p->args = args;
20 }
21 
22 //
23 // actual "parse" method
24 //
25 static void  _parse( parse_context *p)
26 {
27    NSString  *s;
28 
29    s = va_arg( *p->args, NSString *);
30    NSLog( @"%@", s);
31 }
32 
33 
34 //
35 // convenience method (which crashes)
36 //
37 static void   parse( va_list args)
38 {
39    parse_context   context;
40    
41    /// ***
42    /// *** WARNING see below ***
43    init_parse_context( &context, &args); 
44    /// ***
45    /// ***
46    
47    _parse( &context);
48 }
49 
50 
51 @interface Foo : NSObject
52 
53 + (void) test:(NSString *) format, ...;
54 
55 @end
56 
57 
58 @implementation Foo
59 
60 + (void) test:(NSString *) format, ...
61 {
62    va_list         args;
63    parse_context   context;
64    
65    // this is OK
66    va_start( args, format);
67    init_parse_context( &context, &args);
68    _parse( &context);
69    va_end( args);
70    
71    // this is not
72    va_start( args, format);
73    parse( args);
74    va_end( args);
75 }
76 
77 @end

You probably need the help of the compiler. It produces the following warning - rightfully so, the resultant program crashes:

warning: incompatible pointer types passing '__va_list_tag **' (aka 'struct __va_list_tag **') to parameter of type 'va_list *' (aka '__builtin_va_list *') [-Wincompatible-pointer-types]
   init_parse_context( &context, &args);
                                 ^~~~~
more.m:19:43: note: passing argument to parameter 'args' here
                                 va_list *args)
                                          ^
1 warning generated.

Now why is this weird ? Obviously parse() just does, what is done in +test directly namely:

init_parse_context( &context, &args);
_parse( &context);

What is interesting is is, that a "passed" va_list isn't of type va_list anymore but instead of type struct __va_list_tag *. Weird and unexpected and in terms of my mental model of C-types wrong. But maybe necessary in terms of stdarg implementation ?


Post a comment

All comments are held for moderation; basic HTML formatting accepted.

Name:
E-mail: (not published)
Website: