Nat! bio photo

Nat!

Senior Mull

Twitter Github Twitch

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#import <Foundation/Foundation.h>

//
// just a small struct to carry parser context around
// pared down to va_list. (You need to use va_list *)
//
typedef struct
{
   va_list    *args;
} parse_context;


//
// this functions sets the parse_context up
//
static void   init_parse_context( parse_context *p,
                                  va_list *args)
{
   p->args = args;
}

//
// actual "parse" method
//
static void  _parse( parse_context *p)
{
   NSString  *s;

   s = va_arg( *p->args, NSString *);
   NSLog( @"%@", s);
}


//
// convenience method (which crashes)
//
static void   parse( va_list args)
{
   parse_context   context;
   
   /// ***
   /// *** WARNING see below ***
   init_parse_context( &context, &args); 
   /// ***
   /// ***
   
   _parse( &context);
}


@interface Foo : NSObject

+ (void) test:(NSString *) format, ...;

@end


@implementation Foo

+ (void) test:(NSString *) format, ...
{
   va_list         args;
   parse_context   context;
   
   // this is OK
   va_start( args, format);
   init_parse_context( &context, &args);
   _parse( &context);
   va_end( args);
   
   // this is not
   va_start( args, format);
   parse( args);
   va_end( args);
}

@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: