[va_list *] Is this legal ?

M

m.labanowicz

Hello,

I'm trying to compile following example:

-----{test:BEG}----------------------------------------
01: #include <stdio.h>
02: #include <stdarg.h>
03: #include <stdlib.h>
04: int va_get_int(va_list * ap) {
05: return va_arg(*ap, int);
06: }
07: char * va_get_chrptr(va_list * ap) {
08: return va_arg(*ap, char *);
09: }
10: void fooA(va_list ap) {
11: char const * txt = NULL;
12: printf("A:int = %d\n", va_arg(ap, int));
13: txt = va_arg(ap, char *);
14: printf("A:ptr = %p\n", (void *)txt);
15: printf("A:ptr = \"%s\"\n", txt);
16: }
17: void fooB(va_list ap) {
18: char const * txt = NULL;
19: printf("B:int = %d\n", va_get_int(&ap));
20: txt = va_get_chrptr(&ap);
21: printf("B:ptr = %p\n", (void *)txt);
22: printf("B:ptr = \"%s\"\n", txt);
23: }
24: void bar(char const * arg0, ...) {
25: va_list ap;
26: printf("arg0 = \"%s\"\n", arg0);
27: va_start(ap, arg0);
28: fooA(ap);
29: va_end(ap);
30: va_start(ap, arg0);
31: fooB(ap);
32: va_end(ap);
33: va_start(ap, arg0);
34: fooA(ap);
35: va_end(ap);
36: }
37: int main(void) {
38: bar("koko", 23, "txt");
39: return EXIT_SUCCESS;
40: }
-----{test:EOF}----------------------------------------

[32 bit machine]
gcc (Ubuntu 4.4.3-4ubuntu5) 4.4.3
$ gcc -W -Wall -ansi -pedantic test.c
$ ./a.out
arg0 = "koko"
A:int = 23
A:ptr = 0x8048669
A:ptr = "txt"
B:int = 23
B:ptr = 0x8048669
B:ptr = "txt"
A:int = 23
A:ptr = 0x8048669
A:ptr = "txt"

[64 bit machine]
gcc (Ubuntu/Linaro 4.7.2-11precise2) 4.7.2
$ gcc -W -Wall -ansi -pedantic test.c
test.c: In function 'fooB':
test.c:18:3: warning: passing argument 1 of 'va_get_int' from incompatible pointer type [enabled by default]
test.c:4:5: note: expected 'struct __va_list_tag (*)[1]' but argument is of type 'struct __va_list_tag **'
test.c:19:3: warning: passing argument 1 of 'va_get_chrptr' from incompatible pointer type [enabled by default]
test.c:7:8: note: expected 'struct __va_list_tag (*)[1]' but argument is of type 'struct __va_list_tag **'
$ ./a.out
arg0 = "koko"
A:int = 23
A:ptr = 0x40096d
A:ptr = "txt"
B:int = -443987883
B:ptr = 0x455441ed31455541
Segmentation fault (core dumped)

It seems that pointer to 'va_list' can not be used,
ok, I have changed code to:
-----{diff:BEG}----------------------------------------
4,5c4,5
< int va_get_int(va_list * ap) {
< return va_arg(*ap, int);
---
int va_get_int(va_list ap) {
return va_arg(ap, int);
7,8c7,8
< char * va_get_chrptr(va_list * ap) {
< return va_arg(*ap, char *);
---
char * va_get_chrptr(va_list ap) {
return va_arg(ap, char *);
19,20c19,20
< printf("B:int = %d\n", va_get_int(&ap));
< txt = va_get_chrptr(&ap);
---
printf("B:int = %d\n", va_get_int(ap));
txt = va_get_chrptr(ap);
-----{diff:EOF}----------------------------------------

After that both compilers are silent,
no errors, no warnings, but:

[64 bit machine]
gcc (Ubuntu/Linaro 4.7.2-11precise2) 4.7.2
$ gcc -W -Wall -ansi -pedantic test2.c
$ ./a.out
arg0 = "koko"
A:int = 23
A:ptr = 0x40096d
A:ptr = "txt"
B:int = 23
B:ptr = 0x40096d
B:ptr = "txt"
A:int = 23
A:ptr = 0x40096d
A:ptr = "txt"

[32 bit machine]
gcc (Ubuntu 4.4.3-4ubuntu5) 4.4.3
$ gcc -W -Wall -ansi -pedantic test2.c
$ ./a.out
arg0 = "koko"
A:int = 23
A:ptr = 0x8048669
A:ptr = "txt"
B:int = 23
B:ptr = 0x17
Segmentation fault


Best Regards
 
N

Noob

Robert said:
Mind you that there is no guarantee in C that there is *any* integer
type that can actually hold a pointer (although both Posix and Windows
require such).

However, if a given implementation provides intptr_t and/or uintptr_t,
then one can use either(?) to store the representation of an object pointer.

Tangential questions:

- Can uintptr_t store the representation of a function pointer?

- Why provide signed and unsigned types?

Regards.
 
T

Tim Rentsch

I'm trying to compile following example:

-----{test:BEG}----------------------------------------
01: #include <stdio.h>
02: #include <stdarg.h>
03: #include <stdlib.h>
04: int va_get_int(va_list * ap) {
05: return va_arg(*ap, int);
06: }
07: char * va_get_chrptr(va_list * ap) {
08: return va_arg(*ap, char *);
09: }
10: void fooA(va_list ap) {
11: char const * txt = NULL;
12: printf("A:int = %d\n", va_arg(ap, int));
13: txt = va_arg(ap, char *);
14: printf("A:ptr = %p\n", (void *)txt);
15: printf("A:ptr = \"%s\"\n", txt);
16: }
17: void fooB(va_list ap) {
18: char const * txt = NULL;
19: printf("B:int = %d\n", va_get_int(&ap));
20: txt = va_get_chrptr(&ap);
21: printf("B:ptr = %p\n", (void *)txt);
22: printf("B:ptr = \"%s\"\n", txt);
23: }
24: void bar(char const * arg0, ...) {
25: va_list ap;
26: printf("arg0 = \"%s\"\n", arg0);
27: va_start(ap, arg0);
28: fooA(ap);
29: va_end(ap);
30: va_start(ap, arg0);
31: fooB(ap);
32: va_end(ap);
33: va_start(ap, arg0);
34: fooA(ap);
35: va_end(ap);
36: }
37: int main(void) {
38: bar("koko", 23, "txt");
39: return EXIT_SUCCESS;
40: }
-----{test:EOF}----------------------------------------

[32 bit machine] [..snip..]

[64 bit machine]
gcc (Ubuntu/Linaro 4.7.2-11precise2) 4.7.2
$ gcc -W -Wall -ansi -pedantic test.c
test.c: In function 'fooB':
test.c:18:3: warning: passing argument 1 of 'va_get_int' from incompatible pointer type [enabled by default]
test.c:4:5: note: expected 'struct __va_list_tag (*)[1]' but argument is of type 'struct __va_list_tag **'
test.c:19:3: warning: passing argument 1 of 'va_get_chrptr' from incompatible pointer type [enabled by default]
test.c:7:8: note: expected 'struct __va_list_tag (*)[1]' but argument is of type 'struct __va_list_tag **'
[..snip..]

These messages appear not to match the given source file
(some of the line numbers are off). This makes me
suspicious that what is being reported may not match what
really occurred.

Incidentally, I suggest using -pedantic-errors rather than
just -pedantic. The conditions being reported above should
be errors, not warnings; -pedantic-errors should do that.

Also, I think the problem you're having is not taking into
account that the va_list type could be an array type. This
means that a declaration like this

void
blah(){
va_list ap;
}

and a declaration like this

void
bleah( va_list ap ){
}

even though it looks like the same type is involved in both
cases, actually the two types are different. Try changing
the function fooB() so its declaration looks like this

void fooB( va_list *ap ){ /* etc */

and make other changes as needed (the call to fooB(), and
the functions that fooB() calls that take this parameter).
Using the type 'va_list *' should work, _if_ you use it all
the way along the call chain to the function that declares
the original 'va_list ap;' local variable, because that
avoids the problem with having an array type for a parameter
declaration.
 
H

Heikki Kallasjoki

I'm trying to compile following example:

-----{test:BEG}----------------------------------------
01: #include <stdio.h>
02: #include <stdarg.h>
03: #include <stdlib.h>
04: int va_get_int(va_list * ap) {
05: return va_arg(*ap, int);
06: }
07: char * va_get_chrptr(va_list * ap) {
08: return va_arg(*ap, char *);
09: }
10: void fooA(va_list ap) {
11: char const * txt = NULL;
12: printf("A:int = %d\n", va_arg(ap, int));
13: txt = va_arg(ap, char *);
14: printf("A:ptr = %p\n", (void *)txt);
15: printf("A:ptr = \"%s\"\n", txt);
16: }
17: void fooB(va_list ap) {
18: char const * txt = NULL;
19: printf("B:int = %d\n", va_get_int(&ap));
20: txt = va_get_chrptr(&ap);
21: printf("B:ptr = %p\n", (void *)txt);
22: printf("B:ptr = \"%s\"\n", txt);
23: }
24: void bar(char const * arg0, ...) {
25: va_list ap;
26: printf("arg0 = \"%s\"\n", arg0);
27: va_start(ap, arg0);
28: fooA(ap);
29: va_end(ap);
30: va_start(ap, arg0);
31: fooB(ap);
32: va_end(ap);
33: va_start(ap, arg0);
34: fooA(ap);
35: va_end(ap);
36: }
37: int main(void) {
38: bar("koko", 23, "txt");
39: return EXIT_SUCCESS;
40: }
-----{test:EOF}---------------------------------------- [...]
It seems that pointer to 'va_list' can not be used,
ok, I have changed code to:
-----{diff:BEG}----------------------------------------
4,5c4,5
< int va_get_int(va_list * ap) {
< return va_arg(*ap, int);
---
int va_get_int(va_list ap) {
return va_arg(ap, int);
7,8c7,8
< char * va_get_chrptr(va_list * ap) {
< return va_arg(*ap, char *);
---
char * va_get_chrptr(va_list ap) {
return va_arg(ap, char *);
19,20c19,20
< printf("B:int = %d\n", va_get_int(&ap));
< txt = va_get_chrptr(&ap);
---
printf("B:int = %d\n", va_get_int(ap));
txt = va_get_chrptr(ap);
-----{diff:EOF}----------------------------------------

Due to C11 7.16p3:

If access to the varying arguments is desired, the called function
shall declare an object (generally referred to as ap in this
subclause) having type va_list. The object ap may be passed as an
argument to another function; if that function invokes the va_arg
macro with parameter ap, the value of ap in the calling function is
indeterminate and shall be passed to the va_end macro prior to any
further reference to ap.

FOOTNOTE 253) It is permitted to create a pointer to a va_list and
pass that pointer to another function, in which case the original
function may make further use of the original list after the other
function returns.

....the rewritten version seems to be unambiguously illegal. However,
the footnote seems to slightly suggest that the original code ought to
be permitted, though admittedly by a strict reading it does not
guarantee that anything else than the function declaring the va_list may
form the pointer to it, and only pass it to a single other function.
Note that if you rewrite it so that fooB itself also takes a "va_list *"
argument, things (at least on my system) work out right on both 32 and
64 bits; example pasted to:
http://sprunge.us/gabU

(Presumably, and based on the warning messages emitted, the difference
is due to "[1]" meaning different things in a function parameter
declaration than elsewhere.)
 
T

Tim Rentsch

Robert Wessel said:
Hello,

I'm trying to compile following example:

-----{test:BEG}----------------------------------------
01: #include <stdio.h>
02: #include <stdarg.h>
03: #include <stdlib.h>
04: int va_get_int(va_list * ap) {
05: return va_arg(*ap, int);
06: }
07: char * va_get_chrptr(va_list * ap) {
08: return va_arg(*ap, char *);
09: }
10: void fooA(va_list ap) {
11: char const * txt = NULL;
12: printf("A:int = %d\n", va_arg(ap, int));
13: txt = va_arg(ap, char *);
14: printf("A:ptr = %p\n", (void *)txt);
15: printf("A:ptr = \"%s\"\n", txt);
16: }
17: void fooB(va_list ap) {
18: char const * txt = NULL;
19: printf("B:int = %d\n", va_get_int(&ap));
20: txt = va_get_chrptr(&ap);
21: printf("B:ptr = %p\n", (void *)txt);
22: printf("B:ptr = \"%s\"\n", txt);
23: }
24: void bar(char const * arg0, ...) {
25: va_list ap;
26: printf("arg0 = \"%s\"\n", arg0);
27: va_start(ap, arg0);
28: fooA(ap);
29: va_end(ap);
30: va_start(ap, arg0);
31: fooB(ap);
32: va_end(ap);
33: va_start(ap, arg0);
34: fooA(ap);
35: va_end(ap);
36: }
37: int main(void) {
38: bar("koko", 23, "txt");
39: return EXIT_SUCCESS;
40: }
-----{test:EOF}----------------------------------------

[32 bit machine]
gcc (Ubuntu 4.4.3-4ubuntu5) 4.4.3
$ gcc -W -Wall -ansi -pedantic test.c
$ ./a.out
arg0 = "koko"
A:int = 23
A:ptr = 0x8048669
A:ptr = "txt"
B:int = 23
B:ptr = 0x8048669
B:ptr = "txt"
A:int = 23
A:ptr = 0x8048669
A:ptr = "txt"

[64 bit machine]
gcc (Ubuntu/Linaro 4.7.2-11precise2) 4.7.2
$ gcc -W -Wall -ansi -pedantic test.c
test.c: In function 'fooB':
test.c:18:3: warning: passing argument 1 of 'va_get_int' from incompatible pointer type [enabled by default]
test.c:4:5: note: expected 'struct __va_list_tag (*)[1]' but argument is of type 'struct __va_list_tag **'
test.c:19:3: warning: passing argument 1 of 'va_get_chrptr' from incompatible pointer type [enabled by default]
test.c:7:8: note: expected 'struct __va_list_tag (*)[1]' but argument is of type 'struct __va_list_tag **'
$ ./a.out
arg0 = "koko"
A:int = 23
A:ptr = 0x40096d
A:ptr = "txt"
B:int = -443987883
B:ptr = 0x455441ed31455541
Segmentation fault (core dumped)

It seems that pointer to 'va_list' can not be used,
ok, I have changed code to:
-----{diff:BEG}----------------------------------------
4,5c4,5
< int va_get_int(va_list * ap) {
< return va_arg(*ap, int);
---
int va_get_int(va_list ap) {
return va_arg(ap, int);
7,8c7,8
< char * va_get_chrptr(va_list * ap) {
< return va_arg(*ap, char *);
---
char * va_get_chrptr(va_list ap) {
return va_arg(ap, char *);
19,20c19,20
< printf("B:int = %d\n", va_get_int(&ap));
< txt = va_get_chrptr(&ap);
---
printf("B:int = %d\n", va_get_int(ap));
txt = va_get_chrptr(ap);
-----{diff:EOF}----------------------------------------

After that both compilers are silent,
no errors, no warnings, but:

[64 bit machine]
gcc (Ubuntu/Linaro 4.7.2-11precise2) 4.7.2
$ gcc -W -Wall -ansi -pedantic test2.c
$ ./a.out
arg0 = "koko"
A:int = 23
A:ptr = 0x40096d
A:ptr = "txt"
B:int = 23
B:ptr = 0x40096d
B:ptr = "txt"
A:int = 23
A:ptr = 0x40096d
A:ptr = "txt"

[32 bit machine]
gcc (Ubuntu 4.4.3-4ubuntu5) 4.4.3
$ gcc -W -Wall -ansi -pedantic test2.c
$ ./a.out
arg0 = "koko"
A:int = 23
A:ptr = 0x8048669
A:ptr = "txt"
B:int = 23
B:ptr = 0x17
Segmentation fault


On most 64 bit implementations of C, pointers are 64 bits and ints are
32, thus attempting to squeeze a 64 bit address through a 32 bit int
is unlikely to be successful.

On most 64 bit *nix, long is 64 bits, while on windows long remains 32
bits. In both cases explicitly 64 bit types will be provided by the
compiler.

Mind you that there is no guarantee in C that there is *any* integer
type that can actually hold a pointer (although both Posix and Windows
require such).

What makes you think this has anything to do with his
problem?
 
T

Tim Rentsch

Heikki Kallasjoki said:
I'm trying to compile following example: [snip]

Due to C11 7.16p3:

If access to the varying arguments is desired, the called function
shall declare an object (generally referred to as ap in this
subclause) having type va_list. The object ap may be passed as an
argument to another function; if that function invokes the va_arg
macro with parameter ap, the value of ap in the calling function is
indeterminate and shall be passed to the va_end macro prior to any
further reference to ap.

FOOTNOTE 253) It is permitted to create a pointer to a va_list and
pass that pointer to another function, in which case the original
function may make further use of the original list after the other
function returns.

...the rewritten version seems to be unambiguously illegal. However,
the footnote seems to slightly suggest that the original code ought to
be permitted,

The problem is what that code is making is not a pointer to a
va_list but (in some cases) a pointer to a different type, and
the result of that is not compatible with a va_list *.
though admittedly by a strict reading it does not
guarantee that anything else than the function declaring the va_list may
form the pointer to it, and only pass it to a single other function.
Note that if you rewrite it so that fooB itself also takes a "va_list *"
argument, things (at least on my system) work out right on both 32 and
64 bits; [snip]
(Presumably, and based on the warning messages emitted, the difference
is due to "[1]" meaning different things in a function parameter
declaration than elsewhere.)

Yes. And the moral is, _always_ use 'va_list *' for parameters,
because the behavior is more well-defined than for plain 'va_list',
and it won't lead to nasty little surprises like the examples here
(which do use 'va_list' for parameters) illustrate.
 
M

m.labanowicz

These messages appear not to match the given source file
(some of the line numbers are off). This makes me
suspicious that what is being reported may not match what
really occurred.

Sorry, my fault, I hate such inconsistency, following there is the corresponding compilation messages to the code from original post:
----------------------------------
test.c: In function ‘fooB’:
test.c:19:3: warning: passing argument 1 of ‘va_get_int’ from incompatible pointer type [enabled by default]
test.c:4:5: note: expected ‘struct __va_list_tag (*)[1]’ but argument is of type ‘struct __va_list_tag **’
test.c:20:3: warning: passing argument 1 of ‘va_get_chrptr’ from incompatible pointer type [enabled by default]
test.c:7:8: note: expected ‘struct __va_list_tag (*)[1]’ but argument is of type ‘struct __va_list_tag **’
 
S

Shao Miller

However, if a given implementation provides intptr_t and/or uintptr_t,
then one can use either(?) to store the representation of an object pointer.

Tangential questions:

- Can uintptr_t store the representation of a function pointer?

I don't see a guarantee for that, no.
- Why provide signed and unsigned types?

Since pointer representation is mostly opaque, this strikes me as a
flexibility for the implementation. Suppose an implementation
"actually" uses a signed integer value for pointers... Then not
providing 'uintptr_t' and providing 'intptr_t' might be most natural.
Does that make sense?

- Shao Miller
 
S

Shao Miller

Hello,

I'm trying to compile following example:

'va_list' is an array type. A function parameter cannot be declared to
have an array type, even if it looks like it does. Here are two
test-cases for you:

#include <stdio.h>

typedef char arr42_t[42];

void func1(arr42_t * arr) {
(*arr)[0] = 'C';
(*arr)[1] = '\0';
}

void func2(arr42_t arr) {
func1(&arr);
}

int main(void) {
arr42_t arr;

func2(arr);
printf("%s\n", arr);
return 0;
}

---

gcc -ansi -pedantic -Wall -Wextra -Werror -o mlaban1 mlaban1.c

---

#include <stdio.h>

typedef char arr42_t[42];

void func1(arr42_t * arr) {
(*arr)[0] = 'C';
(*arr)[1] = '\0';
}

void func2(arr42_t * arr) {
func1(arr);
}

int main(void) {
arr42_t arr;

func2(&arr);
printf("%s\n", arr);
return 0;
}

---

gcc -ansi -pedantic -Wall -Wextra -Werror -o mlaban2 mlaban2.c

---

And here is a test-case to demonstrate that the parameter is not the
same type as the original:

#include <stdio.h>

typedef char arr42_t[42];

size_t sizeof_arr_param(arr42_t param) {
return sizeof param;
}

int main(void) {
arr42_t arr;

printf(
"sizeof arr == %u && sizeof param == %u\n",
(unsigned int) sizeof arr,
(unsigned int) sizeof_arr_param(arr)
);
return 0;
}
 
K

Keith Thompson

Shao Miller said:
I don't see a guarantee for that, no.

There is no such guarantee. You can convert a void* to intptr_t or
uintptr_t and back to void*, and get back a result that compares
equal to the original value. A void* may not be able to hold the
converted value of a function pointer without loss of information.
Since pointer representation is mostly opaque, this strikes me as a
flexibility for the implementation. Suppose an implementation
"actually" uses a signed integer value for pointers... Then not
providing 'uintptr_t' and providing 'intptr_t' might be most natural.
Does that make sense?

Yes, but that doesn't seem to be the way the standard defines it.
Both intptr_t and uintptr_t behave as described. Both types
are optional, but there's no guidance for the criteria for
an implementation to provide them or not. Typically (with
two's-complement and no padding bits) a signed type and its
corresponding unsigned type hold exactly the same amount of
information, and I've never heard of an implementation that provides
intptr_t but not uintptr_t, or vice versa.

And if either intptr_t or uintptr_t provides a more natural mapping
to pointers on a given implementation, I see no way for a program
to detect that.

On the other hand, if you're using these types, you're probably
writing low-level implementation-specific code anyway, so you should
know (by other means) which type is more useful.
 
T

Tim Rentsch

Keith Thompson said:
and I've never heard of an implementation that provides
intptr_t but not uintptr_t, or vice versa.

If one is defined the other must be -- 7.20.1 p1.
 
K

Keith Thompson

Tim Rentsch said:
If one is defined the other must be -- 7.20.1 p1.

Ah, I missed that.

And the standard provides no guidance about which type might be
more appropriate for a given system.

It could matter, in the sense that a system where pointers
are naturally unsigned might permit an object to span the range
0x7fffffff..0x80000000, while a system where pointers are naturally
signed probably wouldn't. And you might want to do meaningful "<"
and ">" comparisons on intptr_t values.

But again, if you're writing such low-level code, you're probably
depending on non-standard information about the characteristics of
pointers anyway.

7.20.1p1 also implies that, for example, a ones'-complement
system that can't provide a conforming int32_t (which must be
two's-complement) would not be allowed to define uint32_t, even
if it could do so meaningfully. I think that's a mistake, but not
one that's rear its head in real life.
 
G

glen herrmannsfeldt

(snip, someone wrote)
(snip)

Yes, but that doesn't seem to be the way the standard defines it.
Both intptr_t and uintptr_t behave as described. Both types
are optional, but there's no guidance for the criteria for
an implementation to provide them or not. Typically (with
two's-complement and no padding bits) a signed type and its
corresponding unsigned type hold exactly the same amount of
information, and I've never heard of an implementation that provides
intptr_t but not uintptr_t, or vice versa.
And if either intptr_t or uintptr_t provides a more natural mapping
to pointers on a given implementation, I see no way for a program
to detect that.

I was about to write that I didn't know any implementations with
negative addresses, but the VAX addressing space has user space
allocated up from 0, and system space down from X'FFFFFFFF'.

It might, then, be natural to consider system space as small negative
values instead of large unsigned values.
On the other hand, if you're using these types, you're probably
writing low-level implementation-specific code anyway, so you should
know (by other means) which type is more useful.

-- glen
 
G

glen herrmannsfeldt

Ah, I missed that.
And the standard provides no guidance about which type might be
more appropriate for a given system.
It could matter, in the sense that a system where pointers
are naturally unsigned might permit an object to span the range
0x7fffffff..0x80000000, while a system where pointers are naturally
signed probably wouldn't. And you might want to do meaningful "<"
and ">" comparisons on intptr_t values.


Then again, a system might not allow for either one. As I previously
mentioned, VAX, for one, defines two (actually four based on the two
high bits) separate address spaces.
But again, if you're writing such low-level code, you're probably
depending on non-standard information about the characteristics of
pointers anyway.
7.20.1p1 also implies that, for example, a ones'-complement
system that can't provide a conforming int32_t (which must be
two's-complement) would not be allowed to define uint32_t, even
if it could do so meaningfully. I think that's a mistake, but not
one that's rear its head in real life.

All the sign magnitude and ones complement systems that I know about
aren't 32 bit systems. The CDC machines use 60 bits, though I believe
that addresses don't use all the bits. The IBM 36 bit machines, such
as the 7090, are sign magnitude, but not all the bits are used for
integer arithmetic. I believe they use 15 bits, plus the sign bit,
but store the value in a 36 bit word.

There might be at least one of each running, somewhere in the world.

-- glen
 
A

Andrey Tarasevich

And if either intptr_t or uintptr_t provides a more natural mapping
to pointers on a given implementation, I see no way for a program
to detect that.

On the other hand, if you're using these types, you're probably
writing low-level implementation-specific code anyway, so you should
know (by other means) which type is more useful.

Not necessarily low-level. I would 'uintptr_t' type as natural candidate
for generic "counter" type for entities that can exist in "unlimited"
quantities in the storage.

For example, 'size_t' is often used as a natural choice of "counter"
type for generic arrays. Since the number of bytes in an array object
does not exceed the range of 'size_t', 'size_t' should always be
sufficient to index and count any array.

Similar reasoning can be applied to 'uintptr_t' and non-array
containers. Since the number of entries in a generic linked list cannot
exceed the number of bytes in the storage (and it is reasonable to
expect that to be in 'uintptr_t's range) 'uintptr_t' should be
sufficient to index and count any linked list (or any other
non-array-based container).

In other words, even though the intended semantics of 'uintptr_t' type
is relatively low-level, it has some pretty reasonable uses within
higher-level concepts (again, pretty much the way it is with 'size_t').
 
K

Keith Thompson

glen herrmannsfeldt said:
All the sign magnitude and ones complement systems that I know about
aren't 32 bit systems. The CDC machines use 60 bits, though I believe
that addresses don't use all the bits. The IBM 36 bit machines, such
as the 7090, are sign magnitude, but not all the bits are used for
integer arithmetic. I believe they use 15 bits, plus the sign bit,
but store the value in a 36 bit word.

There might be at least one of each running, somewhere in the world.

Sure, but I doubt that there's a conforming C99 or C11 implementation
for them.

But if there were a conforming implementation for a CDC machine, it
could probably support uint60_t but not int60_t -- which means they
wouldn't be allowed to define uint60_t.
 
G

glen herrmannsfeldt

(snip, I wrote)
Sure, but I doubt that there's a conforming C99 or C11 implementation
for them.

I wouldn't complain about a C89 implementation.
But if there were a conforming implementation for a CDC machine, it
could probably support uint60_t but not int60_t -- which means they
wouldn't be allowed to define uint60_t.

I did use the CDC machine some, but not all that much, and not quite
enough to know the answer to that.

I seem to remember some machine that would add and subtract all 60 bits,
but multiply and divide only 48 bits.

I don't actually know if they do unsigned arithmetic.

The CDC machines were supposed to be used when you wanted fast floating
point, and weren't especially useful if you didn't.

-- glen
 
M

m.labanowicz

'va_list' is an array type. A function parameter cannot be declared to
have an array type, even if it looks like it does.

You are right.

I have tried with following code and it seems that it works correctly everywhere:

....
#include <string.h>
typedef struct {
va_list ap;
} va_list_ex_t;
int va_get_int(va_list ap, va_list_ex_t * vle) {
int result = va_arg(ap, int);
memcpy(&(vle->ap), &ap, sizeof(ap));
return result;
}
char * va_get_chrptr(va_list ap, va_list_ex_t * vle) {
char * result = va_arg(ap, char *);
memcpy(&(vle->ap), &ap, sizeof(ap));
return result;
}
void fooB(va_list ap) {
va_list_ex_t vle;
char const * txt = NULL;
printf("B:int = %d\n", va_get_int(ap, &vle));
memcpy(&ap, &(vle.ap), sizeof(ap));
txt = va_get_chrptr(ap, &vle);
memcpy(&ap, &(vle.ap), sizeof(ap));
printf("B:ptr = %p\n", (void *)txt);
printf("B:ptr = \"%s\"\n", txt);
}
....

I think there is no possibility to detect va_list type (struct/array) during preprocessing time and final conclusion is:
'va_list *' type can not be used (it is not portable).
 
M

m.labanowicz

typedef struct {
va_list ap;
} va_list_ex_t;

Above struct is wrong in case va_list for example is declared as:
typedef char va_list [1];

In this case there will be segfault during memcpy.

Fix for that:
typedef union {
va_list ap;
va_list * ptr_to_ap;
void * ptr_general;
} va_list_ex_t;
 
S

Shao Miller

You are right.

I have tried with following code and it seems that it works correctly everywhere:

...
#include <string.h>
typedef struct {
va_list ap;
} va_list_ex_t;
int va_get_int(va_list ap, va_list_ex_t * vle) {
int result = va_arg(ap, int);
memcpy(&(vle->ap), &ap, sizeof(ap));
return result;
}
char * va_get_chrptr(va_list ap, va_list_ex_t * vle) {
char * result = va_arg(ap, char *);
memcpy(&(vle->ap), &ap, sizeof(ap));
return result;
}
void fooB(va_list ap) {
va_list_ex_t vle;
char const * txt = NULL;
printf("B:int = %d\n", va_get_int(ap, &vle));
memcpy(&ap, &(vle.ap), sizeof(ap));
txt = va_get_chrptr(ap, &vle);
memcpy(&ap, &(vle.ap), sizeof(ap));
printf("B:ptr = %p\n", (void *)txt);
printf("B:ptr = \"%s\"\n", txt);
}
...

I think there is no possibility to detect va_list type (struct/array) during preprocessing time and final conclusion is:
'va_list *' type can not be used (it is not portable).

Did you mean 'va_list' type is not portable, or did you really mean
'va_list *' is not portable?

Whether 'va_list' is an array type like it is in your scenario or is
something else for a given implementation, 'va_list *' is explicitly
mentioned in the Standard as being usable, and even has some advantages
which I saw others already mentioned, elsethread.

The trick is to pass '&ap' from your function with the ellipsis ', ...)'
('bar', in your example) and to use '*pap' in any called function that
has 'va_list * pap' as a parameter.

int va_get_int(va_list * pap) {
return va_arg(*pap, int);
}

void fooB(va_list * pap) {
char const * txt = NULL;
printf("B:int = %d\n", va_get_int(pap));
txt = va_get_chrptr(pap);
printf("B:ptr = %p\n", (void *)txt);
printf("B:ptr = \"%s\"\n", txt);
}

void bar(char const * arg0, ...) {
va_list ap;
printf("arg0 = \"%s\"\n", arg0);
va_start(ap, arg0);
fooA(&ap);
fooB(&ap);
fooA(&ap);
va_end(ap);
}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,055
Latest member
SlimSparkKetoACVReview

Latest Threads

Top