2

I'm reading the book Head First C and I am the part about the variable arguments.

I wrote the following code:

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

enum drink {
    MUDSLIDE, FUZZY_NAVEL, MONKEY_GLAND, ZOMBIE
};

double price(enum drink d) {
    switch(d) {
    case MUDSLIDE:
        return 6.79;
    case FUZZY_NAVEL:
        return 5.31;
    case MONKEY_GLAND:
        return 4.82;
    case ZOMBIE:
        return 5.89;
    }
    return 0;
}

double calc(int args, ...) {
    double total = 0;

    va_list ap;
    va_start(ap, args);

    int i;
    for (i = 0; i < args; i++) {
        int currentDrink = va_arg(ap, int);
        total += price((drink) currentDrink);
    }

    va_end(ap);
    return total;
}

int main() {
    printf("Price is %.2f\n", calc(2, MONKEY_GLAND, MUDSLIDE));
    return 0;
}

The code compiles and works perfectly.

But... There are two different lines of my solution with the book.

My:

int currentDrink = va_arg(ap, int);
total += price((drink) currentDrink);

Book:

enum drink currentDrink = va_arg(ap, enum drink);
total += price(currentDrink);

I tried to use the solution proposed in the book, but the error during execution and reports a warning: 'drink' is promoted to 'int' when passed through '...'

The book is used gcc compiler on linux. I am using gcc on Windows.

Question: What is the reason I was unable to compile the code proposed in the book?

Edit There configured wrong. Was using a C++ compiler was thinking of using a C. But the question remains: why in C++ results in a warning and error in the execution?

11
  • 2
    I can't replicate your problem. The book version works fine for me. What version of gcc are you using? Run gcc --version. Commented Jul 4, 2014 at 20:51
  • Please provide your version information for your compiler. Also, for the sake of style and simplicity, try using this for your enum definition: typedef enum drink { MUDSLIDE, FUZZY_NAVEL, MONKEY_GLAND, ZOMBIE } drink;. That way, you can just use drink as a type, rather than having to type enum drink. Commented Jul 4, 2014 at 20:54
  • @ooga gcc (tdm-1) 4.5.2 Commented Jul 4, 2014 at 20:55
  • This seems to be a discrepance between C and C++. Compiling as C worked perfectly, while compiling as C++ shows your warning. Make sure you are compiling as C (by making sure the file has a .c extension). Can somebody find a source in the standard for this, or is it really a compiler problem? Commented Jul 4, 2014 at 20:58
  • 1
    @KelperBR (I'm too slow to edit..) Also make sure you set up your compiler correctly: Settings -> Compiler... -> Toolchain executable -> C Compiler: This should be <whatever>gcc.exe (probably mingw32-gcc.exe), not <whatever>g++.exe. Commented Jul 4, 2014 at 21:13

1 Answer 1

7

Variadic arguments to functions are subject to a conversion called default argument promotions: Everything with a conversion rank smaller than that of int gets promoted to int before passed to a variadic function. And an enum has a smaller conversion rank (and may be represented as a short or something else in your case). So what your callee sees is an int and must fetch it by va_arg as such.

C99 7.15.1.1 p.2 (emphasis mine) about va_arg:

[…] If there is no actual next argument, or if type is not compatible with the type of the actual next argument (as promoted according to the default argument promotions), the behavior is undefined, except for the following cases:

  • one type is a signed integer type, the other type is the corresponding unsigned integer type, and the value is representable in both types;
  • [sth. about pointer types]

And 6.7.2.2, p.4:

Each enumerated type shall be compatible with char, a signed integer type, or an unsigned integer type. The choice of type is implementation-defined, […]

So, the va_arg(ap, enum drink) version has no defined bahavior (as far as the C standard is concerned). At least, if the compiler doesn't specify to always use int for enums. Hence the warning from gcc.

Some coding-guidelines say to avoid enum usage for types entirely, using int everywhere and only define enum constants:

enum {
    MUDSLIDE, FUZZY_NAVEL, MONKEY_GLAND, ZOMBIE
};

and

double price(int d);

HTH

Sign up to request clarification or add additional context in comments.

11 Comments

Your answer is confusing me. When you talk about "his version", you mean the one where he uses va_arg(ap, int). By argument promotion the passed enum drink will be promoted to int, therefore this macro call will be correct. In the book version, va_arg(ap, enum drink) is used and §6.7.2.2 says that enum drink is compatible with int (which is the actual (promoted) type of the argument), therefore you cannot say that it is undefined behaviour.
@KeplerBR This is basically your answer: The book version is no well-defined C (it is platform dependent; only correct if your enum type is implemented with int type); It is also no well-defined C++ (C++ Working Draft N3242, §7.2 p.6 basically says the same as §6.7.2.2 p.4). I guess the reason the C++ compiler gives a warning while the C compiler doesn't is simply because the C compiler historically is far less complex - because that way, it can be maintained much easier. Such a warning could be seen as clutter and there are specific code analysis programs, so "it's not really needed".
@KeplerBR The problem of enum types is that their underlying types are not clearly defined. It could be a char, a short, an unsigned short, etc. In certain situations, to be more precise, situations where the size of the type counts, it can be a hassle to use enum types. Basically everything that uses sizeof x, where x is/has an enumeration type is likely an error. va_args is an example of a macro which uses sizeof, and breaks because of the vagueness of enum types. Personally, I wouldn't discourage use of enum types, as long as it adds readability (but there is also typedef).
Sorry for the excavation, @Anthales, just in case your interested: I think I see the difference between C and C++ now: In C, enum are compatible with some implementation-defined type (for gcc: unsigned int), in C++, they are a distinct type, being promoted to the first of int, unsigned int, long, ..., so the enum is never a type which can be fetched by va_arg; in C, it is valid as long the underlying type is not smaller than int.
@mafso: I think you are right! After some search I found this: "(...) [the] argument’s type must be self-promoting: that is, the default promotions must not change its type. (...) This is actually an ISO C requirement.". While this is about C, C++ is compatible with C variadic functions (it actually refers to the ISO C standard). So the difference here is that enum types in C can be self-promoting, but in C++ they never can. That's probably the reason g++ has a default warning and even aborts the program.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.