P 155 k&r section 7.3

M

mdh

Hi All,
The section is titled Variable-length Argument lists.
May I ask a question about the use of "macros".
To quote K&R.

"The standard header <stdarg.h> contains a set of macro definitions
that define how to step through an argument list...... The type
va_list is used to declare a variable...in minprintf..ap
.......The macro va_start initializes ap to point to the first unnamed
argument."

Could anyone help me understand the significance of using macros vs
functions, as I think it is more significant than I realize.

thanks in advance.
 
K

Keith Thompson

mdh said:
The section is titled Variable-length Argument lists.
May I ask a question about the use of "macros".
To quote K&R.

"The standard header <stdarg.h> contains a set of macro definitions
that define how to step through an argument list...... The type
va_list is used to declare a variable...in minprintf..ap
......The macro va_start initializes ap to point to the first unnamed
argument."

Could anyone help me understand the significance of using macros vs
functions, as I think it is more significant than I realize.

The reason va_start, va_arg, va_end, and va_copy are defined as macros
is that they *can't* be defined as functions.

For example, va_arg takes two arguments, an expression of type va_list
(I'm actually not sure it can be an arbitrary expression) and a type
name. It yields a result of the named type. A C function cannot take
a type name as an argument, only an expression, and it must return a
result of some single type that's specified when the function is
declared.

Now va_arg can't *portably* be defined as a macro either -- but
there's always some way to define it non-portably. In some cases, it
might even be necessary for the compiler to provide some extension
just for the purpose, and have the va_arg macro use that extension;
for example, an implementation might have something like

#define va_arg(ap, type) __builtin_va_arg__(ap, type)
 
M

mdh

The reason va_start, va_arg, va_end, and va_copy are defined as macros
is that they *can't* be defined as functions.

For example, va_arg takes two arguments, an expression of type va_list
(I'm actually not sure it can be an arbitrary expression) and a type
name.  It yields a result of the named type.  A C function cannot take
a type name as an argument, only an expression,



So, in the example that is given on p156,

void minprintf(char *fmt, ...){

va_list ap;

? This declares ap a pointer of type va_list? And the reason ap is a
pointer is simply because that is the way it is defined??


Then,

va_start(ap, fmt);

So, from what you say, if I understand this, the type here (fmt) is
"pointer to char" and the expression 'ap' is initialized or "returned"
as a pointer to char (to the first argument)?

And just to make sure, an example here of va_arg use.


ival is declared an int.

so the expression

ival=va_arg(ap, int) will return a value ( ival) that is of
whatever_type ....in this case int, but could be char *, char etc?


So, although each of these macros does something a little different,
the common thread seems to be that the type of the argument is
unknown, and these macros provide a mechanism to deal with it?
 
K

Keith Thompson

mdh said:
So, in the example that is given on p156,

void minprintf(char *fmt, ...){

va_list ap;

? This declares ap a pointer of type va_list? And the reason ap is a
pointer is simply because that is the way it is defined??

What makes you think va_list is a pointer type? The standard merely
says that it's "an object type suitable for holding information needed
by the macros va_start, va_arg, va_end, and va_copy". It could be a
pointer type, but it could just as easily be a structure or an array.
Then,

va_start(ap, fmt);

So, from what you say, if I understand this, the type here (fmt) is
"pointer to char" and the expression 'ap' is initialized or "returned"
as a pointer to char (to the first argument)?

va_start doesn't return anything. It initializes ap, an object of
type va_list. (That's part of the reason it's a macro; a function
can't modify an argument.) The va_list object exists to allow access
to the variadic arguments; you can think of it as a kind of abstract
index into the argument list.

The second argument to va_start is the name of the parameter just
before the ", ...".

The way in which this provides access to the following parameters is
entirely implementation-specific. If arguments are passed on a stack,
for example, a va_list object might be a pointer that gets advanced
through the region of memory containing the parameter objects. Or it
might be pure compiler magic.
And just to make sure, an example here of va_arg use.


ival is declared an int.

so the expression

ival=va_arg(ap, int) will return a value ( ival) that is of
whatever_type ....in this case int, but could be char *, char etc?

Right. Before calling va_arg, you have to already *know* what the
type of the next argument is going to be, and you have to know when to
stop. For something like printf, this is specified by the format
string. Or all the variadic arguments might be of the same pointer
type, with a null pointer marking the end of the list. Other schemes
are possible.

If you get the type wrong, for example if you call va_arg(ap, int)
when the next argument is actually of type char*, then you've just
entered the realm of undefined behavior.
So, although each of these macros does something a little different,
the common thread seems to be that the type of the argument is
unknown, and these macros provide a mechanism to deal with it?

Right.
 
M

mdh

What makes you think va_list is a pointer type?

I was **deceived**!!!!

In K&R, va_list ap;

is followed by a comment

/*points to each unnamed arg in turn*/

so I assumed ap must be a pointer. :)


 The standard merely
says that it's "an object type suitable for holding information needed
by the macros va_start, va_arg, va_end, and va_copy".  It could be a
pointer type, but it could just as easily be a structure or an array.






va_start doesn't return anything.  It initializes ap, an object of
type va_list.  (That's part of the reason it's a macro; a function
can't modify an argument.)


The second argument to va_start is the name of the parameter just
before the ", ...".

The way in which this provides access to the following parameters is
entirely implementation-specific.  If arguments are passed on a stack,
for example, a va_list object might be a pointer that gets advanced
through the region of memory containing the parameter objects.  Or it
might be pure compiler magic.

thank you Keith.
 
M

mdh

The reason va_start, va_arg, va_end, and va_copy are defined as macros
is that they *can't* be defined as functions.

May I pursue another issue a little further.

K&R (p 155) declare minprintf thus:

void minprintf(char *fmt, ...);


When I followed the example, it did not seem to matter to the output
whether the declaration was

void minprintf(char *fmt, ...);


or

void minprintf(int fmt, ...);


The only difference is ( maybe the **only** will raise some chuckles)
that char *fmt points to all the arguments in my debugger, but int
returns an integer( surprise, surprise!). For fear of flogging a dead
horse, is that the difference?
 
K

Kenny McCormack

May I pursue another issue a little further.

K&R (p 155) declare minprintf thus:

void minprintf(char *fmt, ...);


When I followed the example, it did not seem to matter to the output
whether the declaration was

void minprintf(char *fmt, ...);


or

void minprintf(int fmt, ...);

On many machines 'int' and 'char *' are the same size, have the same bit
representation, and are, basically, interchangeable. You can't rely on
this being true on all machines, of course...
 
B

Barry Schwarz

May I pursue another issue a little further.

K&R (p 155) declare minprintf thus:

void minprintf(char *fmt, ...);


When I followed the example, it did not seem to matter to the output
whether the declaration was

void minprintf(char *fmt, ...);


or

void minprintf(int fmt, ...);

How were you able to check the first argument for '%' followed by 'd',
'f', or 's' if its type was int instead of char*?
The only difference is ( maybe the **only** will raise some chuckles)
that char *fmt points to all the arguments in my debugger, but int

What does this mean? How can a pointer in your code point to
something in your debugger? For that matter, how do you even pass
arguments to a debugger?
returns an integer( surprise, surprise!). For fear of flogging a dead

Arguments don't "return" anything. Did you mean your debugger
displayed an integer?
horse, is that the difference?

What difference? Show your code for a minprintf with an int as its
first parameter and a sample calling statement.

As coded in K&R, minprintf will handle variadic arguments (the ones
implied by the ...) of type int, double, and char* based on the
conversion specification in the format string.

If you change the parameter and argument to an int, how will minprintf
know the type of the next argument to extract? How will it know how
many arguments to extract?
 
M

mdh

On many machines 'int' and 'char *' are the same size, have the same bit
representation, and are, basically, interchangeable.  You can't rely on
this being true on all machines, of course...


thanks Kenny..there is clearly something I am not seeing...but
hopefully this will become clearer.
 
M

mdh

How were you able to check the first argument for '%' followed by 'd',
'f', or 's' if its type was int instead of char*?


I guess that is what I do not get.

What does this mean?

What I mean ( not put too articulately) is that as I step through the
debugger, fmt is displayed, the value of which is the entire argument
in minprintf if fmt is declared at char *fmt, but an integer if
declared as int.
Arguments don't "return" anything.  Did you mean your debugger
displayed an integer?

correct.



What difference?  Show your code for a minprintf with an int as its
first parameter and a sample calling statement.

As coded in K&R, minprintf will handle variadic arguments (the ones
implied by the ...) of type int, double, and char* based on the
conversion specification in the format string.

If you change the parameter and argument to an int, how will minprintf
know the type of the next argument to extract?  

Well...that is what I am clearly not getting. I **thought** that the
essence of the ellipse ( ...) was that one does not know what the type
and number of the arguments is?
How will it know how many arguments to extract?  

Are you saying that the role of the declaration "char *fmt" is to
tell the compiler that each argument needs to be extracted as a char?
 
H

Harald van Dijk

I guess that is what I do not get.

Then, please post a minimal complete program that includes the declaration
void minprintf(int fmt, ...);, defines that function exactly the way you
have defined it now, and calls it.
 
K

Keith Thompson

mdh said:
Well...that is what I am clearly not getting. I **thought** that the
essence of the ellipse ( ...) was that one does not know what the type
and number of the arguments is?


Are you saying that the role of the declaration "char *fmt" is to
tell the compiler that each argument needs to be extracted as a char?

No.

Here's a sample definition; I don't know whether it exactly matches
what's in K&R, but it's close enough to make the point.

int minprintf(char *fmt, ...)
{
/* ... */
}

The *only* thing the "char *fmt, ..." part of the declaration tells
the compiler is that the first argument is of type char*, and there
will be zero or more arguments of unspecified type(s) following that.
The compiler has absolutely no way of knowing anything more than that.

Which means that it's entirely up to the programmer to make sure that
the arguments passed by the caller and the parameters used by the
callee are consistent.

In printf-like functions, the fmt argument points to a format string
which specifies (not in C, but in a small specialized "language" of
its own) the types of the remaining arguments. For example, if a call
looks like this:

minprintf("%s = %d\n", "foobar", 42);

then minprintf itself needs to make the following calls to fetch the
parameter values:

va_list ap;
va_start(ap, fmt);
va_arg(ap, char*);
va_arg(ap, int);
va_end(ap);

How does it know to call va_arg with char* and then with int? The
format string tells it; it needs to respond to "%s" by calling
va_arg(ap, char*), and to "%d" by calling va_arg(ap, int).

If the first argument were an int rather than a char*, then minprintf
wouldn't have any way of knowing what argument values to grab.

And if the caller accidentally passes inconsistent arguments:

minprintf("%s = %s\n", "foobar", 42);

or if minprintf itself handes the arguments incorrectly, for example
by calling va_arg(ap, unsigned long) in response to "%d", then the
behavior is undefined; the compiler can't even diagnose the error.

(As a special case, some compilers, such as gcc, understand the syntax
of printf-like format strings and can diagnose some errors -- but only
if the format string argument is a literal, and only for printf-like
functions.)
 
B

Barry Schwarz

snip


Well...that is what I am clearly not getting. I **thought** that the
essence of the ellipse ( ...) was that one does not know what the type
and number of the arguments is?


Are you saying that the role of the declaration "char *fmt" is to
tell the compiler that each argument needs to be extracted as a char?

No, it does not tell the compiler (though there are some that will
check in the case of standard library functions). It tells the
function being called. (That is why you see messages in this group
that describe undefined behavior when someone passes an argument that
is incompatible with the conversion specification.) Look at the code
on page 156 again. If the conversion specification is %d, then the
code extracts an int. If it is %f, the code extracts a double. If it
is %s, the code extracts a char*. This all happens at run time, not
compile time.

The role of the char *fmt argument is to allow the same function to
handle different types and numbers of arguments. Why do you think
these functions are called variadic? A "regular" function must always
be called with the correct (or compatible) argument types and the
proper number of arguments. The printf and scanf families of
functions are two in the standard library that don't have this
requirement. minprintf is an example of how you could code your own
flexible function.

Now, show us your code (the one with the first parameter declared as
an int) so we can explain what you are doing wrong, even if it is only
misinterpreting what you see in the debugger.
 
M

mdh

Then, please post a minimal complete program that includes the declaration
void minprintf(int fmt, ...);, defines that function exactly the way you
have defined it now, and calls it.


I have been trying to do so for a few hours, but each time I have been
called away! I will try and answer Barry Schwartz if not called again.
 
M

mdh

mdh said:

<snip>








If I just change the declaration to suit yours, my compiler issues a
diagnostic message:

foo.c:7: conflicting types for `minprintf'
and halts the compilation.


as do I
If I change the declaration /and/ the definition, I get a different
diagnostic message:

foo.c:36: warning: passing arg 1 of `minprintf' makes integer from pointer
without a cast
same


Can you convince me that these diagnostic messages have no significance?



Richard...I would not even try!!! :)
I will try and answer BS with an example.
 
M

mdh

No, it does not tell the compiler (though there are some that will
check in the case of standard library functions).  It tells the
function being called.  (That is why you see messages in this group
that describe undefined behavior when someone passes an argument that
is incompatible with the conversion specification.)  Look at the code
on page 156 again.  If the conversion specification is %d, then the
code extracts an int.  If it is %f, the code extracts a double.  If it
is %s, the code extracts a char*.  This all happens at run time, not
compile time.

The role of the char *fmt argument is to allow the same function to
handle different types and numbers of arguments.

..Please see below.

 Why do you think
these functions are called variadic?

Wikipedia:"In computer programming, a variadic function is a function
of variable arity; that is, one which can take different numbers of
arguments."

I think they left out "and of different types".

Now, show us your code (the one with the first parameter declared as
an int) so we can explain what you are doing wrong, even if it is only
misinterpreting what you see in the debugger.

#include <stdio.h>
#include <stdarg.h>

int main (int argc, const char * argv[]) {


/*void minprintf(char *fmt, ...);*/
void minprintf(int fmt, ...);

minprintf("This is a test:\n%d:\n%s:\n%f:\n", 27, "More test",
-90.786);

/*warning: passing argument 1 of minprintf of incompatible pointer
type ( int fmt) */

return 0;
}

void minprintf(int fmt, ...)
/*void minprintf(char *fmt, ...)*/{

/* the role of char *fmt has been explained, but in 2 somewhat
different ways. Keith says "The *only* thing the "char *fmt, ..." part
of the declaration tells the compiler is that the first argument is of
type char*, and there
will be zero or more arguments of unspecified type(s) following that."
And Barry, you say that "The role of the char *fmt argument is to
allow the same function to handle different types and numbers of
arguments." Now my bet is that you are saying the same thing, but it
sounds somewhat different to me.

va_list ap;

/* So as Keith explained, ap is "an object type suitable for holding
information needed
by the macros va_start, va_arg, va_end, and va_copy" */

int ival;
double dval;
char *p, *sval;


va_start(ap, fmt);

/* So, here is the source of my confusion, and clearly the lack of
explaining it to the clc. I think this has been answered, so please
bear with me, but ap is now initialized to point to the first
unnamed argument. As Keith said "If the first argument were an int
rather than a char*, then minprintf wouldn't have any way of knowing
what argument values to grab." So I assume the initialization not only
points ap to the first argument, but it "types" the pointer as well,
and this information is conveyed by the expression "fmt". And,
rhetorically, I know the first argument is of type character, because,
""The *only* thing the "char *fmt, ..." part of the declaration tells
the compiler is that the first argument is of type char*" ( Keith).

For completeness,
debugger output (value of fmt) with declaration 'int fmt' =
1416128883 ( what I see in debugger as I step through the code)
debugger output (value of fmt) with declaration char *fmt = "This is
a test:\n%d:\n%s:\n%f:\n" ( as above)
debugger output (value of *ap) ( int fmt) = 0 '\0'
debugger output (value of *ap) ( char *fmt) = 0 '\0'

*/


for ( p=fmt; *p; p++){

/*So, the first argument, in my case "This is a test:\n%d:\n%s:\n%f:
\n" is now assigned to the char *p */

if ( *p != '%'){
putchar(*p);
continue;
}
switch (*++p) {
case 'd':
ival=va_arg(ap, int);

/* this part I get, I hope :). Each call of va_arg returns 1 argument
and steps ap to the next argument but in addition, types ap correctly
for the expected value, in the above case, and integer*/

printf("%d", ival);
break;
case 'f':
dval=va_arg(ap, double);
printf("%f", dval);
break;
case 's':
for ( sval = va_arg(ap, char*); *sval; sval++)
putchar(*sval);
break;
default:
putchar(*p);
break;
}
}
va_end(ap);
}
 
M

mdh

mdh said:




Once you have understood the message, you should fix your code
appropriately, and recompile.

Hi Richard,
You convinced me **long ago** not to ignore warnings. I think what you
perhaps missed, not that I ignored the warning, but I am trying to
figure out **what** and **why** I am doing with some of the macros.
So, to take your car analogy, it would be like a newly emerged person
from the depths of the Amazon rain forest, who somehow got his license
in England (not sure how far we can stretch this) saying "Why is
there a sign about a blind corner and what's more, what is a blind
corner", and finding out the hard way there is a good reason for
it...and hopefully not becoming undefined in the process!.
 
K

Keith Thompson

mdh said:
Now, show us your code (the one with the first parameter declared as
an int) so we can explain what you are doing wrong, even if it is only
misinterpreting what you see in the debugger.

#include <stdio.h>
#include <stdarg.h>

int main (int argc, const char * argv[]) {


/*void minprintf(char *fmt, ...);*/
void minprintf(int fmt, ...);

minprintf("This is a test:\n%d:\n%s:\n%f:\n", 27, "More test",
-90.786);

/*warning: passing argument 1 of minprintf of incompatible pointer

This warning has nothing to do with the fact that minprintf is
variadic (i.e., has a ", ..." in its declaration). You declared a
function whose first parameter is of type int, and you called it with
a first argument of type char*.
type ( int fmt) */

return 0;
}

void minprintf(int fmt, ...)
/*void minprintf(char *fmt, ...)*/{

/* the role of char *fmt has been explained, but in 2 somewhat
different ways. Keith says "The *only* thing the "char *fmt, ..." part
of the declaration tells the compiler is that the first argument is of
type char*, and there
will be zero or more arguments of unspecified type(s) following that."
And Barry, you say that "The role of the char *fmt argument is to
allow the same function to handle different types and numbers of
arguments." Now my bet is that you are saying the same thing, but it
sounds somewhat different to me.

We're saying two quite different things, both of them true.

I told you what information "char *fmt, ..." conveys *to the
compiler*. If you call minprintf with a first argument of type char*,
and zero or more additional arguments of any type you like, the
compiler won't complain, because you haven't given it enough
information to do so.

Barry told you the *purpose*, in the sense of the program logic, of
the "char *fmt" parameter. Its purpose is to point to a string that
encodes the intended number and type(s) of the following arguments.
The compiler knows nothing about its purpose; it knows only what it
is.

A variadic function has no direct way to determine the number and
type(s) of any arguments corresponding to the "..." in the prototype
-- and yet it must execute just the right sequence of va_arg calls to
extract the values of those arguments. That means it has to have some
indirect way to determine that information. One common method is to
encode the information in a format string. Other methods are
possible.

[snip]
 
B

Ben Bacarisse

mdh said:
On Sep 6, 4:08 pm, Barry Schwarz <[email protected]> wrote:

Wikipedia:"In computer programming, a variadic function is a function
of variable arity; that is, one which can take different numbers of
arguments."

I think they left out "and of different types".

I'd leave it out too. A function that took a variable number of
arguments that all had to be of the same type would still be variadic
and one that always took the same number would not be variadic even if
they could be of different types in different calls (that would be
polymorphic). In other words, the types of the arguments have nothing
to do with the function being (or not being) variadic.

Of course in C a variadic function can take arguments of any type as
well as of any number (provided then required arguments are given).
 
M

mdh

This warning has nothing to do with the fact that minprintf is
variadic (i.e., has a ", ..." in its declaration).

Was just trying to respond to the request of showing the code and
debugger information as fully as possible, without drawing any
inference from that.
We're saying two quite different things, both of them true.

I told you what information "char *fmt, ..." conveys *to the
compiler*.  If you call minprintf with a first argument of type char*,
and zero or more additional arguments of any type you like, the
compiler won't complain, because you haven't given it enough
information to do so.

Barry told you the *purpose*, in the sense of the program logic, of
the "char *fmt" parameter.  Its purpose is to point to a string that
encodes the intended number and type(s) of the following arguments.
The compiler knows nothing about its purpose; it knows only what it
is.

A variadic function has no direct way to determine the number and
type(s) of any arguments corresponding to the

One common method is to
encode the information in a format string.  Other methods are
possible.

Thanks Keith for sticking with me. What I was trying to do was to
relate the "char *fmt", to the "..." and I finally, hopefully, get it,
thanks to your gentle persistence!!!!! :)
 

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

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,050
Latest member
AngelS122

Latest Threads

Top