1

I'm learning C programming and facing a problem. I would like to "extract" a long double from a list of arguments. But, when I ask va_arg to "catch" a long double argument, I have a random error (as it is mentionned in the man about bad va_arg calls). But how would it be possible for me to solve this problem? Here is an example of code illustrating the pb:

void func(va_list *ptr)
{
    long double f;

    f = va_arg(*ptr, long double);
    printf("Value extracted: %Lf\n", f);
    return ;
}

void func2(char *format, ...)
{
    va_list ap;

    va_start(ap, format);
    func(&ap);
    va_end(ap);
    return ;
}

Thanks for your help!

Edit: I know how to use va_arg (at least for simple stuff). As you can see, the function takes a pointer to a va_list as argument. This is because I call va_start in the calling function (and va_end at the end of the same calling function). I just wanted to show you the problem and nothing else.

I have no problem with other data types (int, long, char, char *, etc.). It's just a long double pb.

Edit #2: I wrote a calling function to show you I am not forgetting va_start and va_end. And, to conclude, my problem is that I am trying to code my own printf function and I am dealing with the -Lf conversion. I didn't think it would be necessary to mention it, sorry about this.

CodeKiwi
  • 71
  • 1
  • 9
  • 1
    That's not how you use va_arg. Look at one of the examples in the documentation, e.g. here http://www.cplusplus.com/reference/cstdarg/va_arg/ – Ben Nov 23 '18 at 17:38
  • You're not calling `va_start` nor `va_end`. – Andrew Henle Nov 23 '18 at 17:40
  • 1
    Possible duplicate of [Is va\_start required in variadic arguments for functions?](https://stackoverflow.com/questions/22703657/is-va-start-required-in-variadic-arguments-for-functions) – Andrew Henle Nov 23 '18 at 17:40
  • It's rather unusual for `func` to accept a `va_list*`. The convention is to pass `va_list` directly (see `vprintf`, etc.). (Also, stylistically, the convention would be to give `func` the same name as `func2` but with a `v` in the name.) What is the error you get? How are you calling `func2`? – jamesdlin Nov 23 '18 at 18:11
  • Additionally, I don't have any prolbems with your code; your issue might lie elsewhere. Please post a [Minimal, Complete, and Verifiable example](https://stackoverflow.com/help/mcve). – jamesdlin Nov 23 '18 at 18:18
  • Guys, it's not because you are not able to solve a problem that you should consider it as "off-topic". Maybe you should consider that a question can be interesting even if you don't understand why. I'm in a school and about twenty other students asked me how to solve this problem since I asked the question on stackoverflow. Next time, just try to think more and to read the answers posted by other person, it could be a benefit to you. – CodeKiwi Nov 25 '18 at 20:25

2 Answers2

1

There are numerous issues with your code, including the lack of va_start and va_end, but to address your specific question:

The va_arg function expects the type to be known when it is called; the type argument is you telling it "hey, the argument is this type", not "get the next argument in the list that has this type" which is what I'm guessing you think it does based on your question.

In general, at the assembly level, there really isn't any information about types as they are passed to functions - without debugging information that is. This is why that argument is needed by va_arg, and why variadic functions like printf and scanf require information about types being processed in their format strings.

If designing a variadic function that takes types that may vary, you need to account for the different possible types; a format string is one way.

Govind Parmar
  • 20,656
  • 7
  • 53
  • 85
  • Thanks for your answer. I've edited my first post because theree was a misunderstanding. I have no problem with using variadic functions. I just wanted to avoid you to read a full function with va_start and va_end, but the pointer to a va_list you can see in my function's arguments is not a magic pointer. I just have problems with long double data type. I think I understand what you explained about the "argument extraction". So, if I can't "ask for" a long double should I try to extract the 12 bytes and convert them into a long double on my own? – CodeKiwi Nov 23 '18 at 17:52
1

You still haven't shown us the complete, compilable code you are using, so it's difficult to see what you're doing wrong. Here's some complete code which illustrates what you're trying to do.

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

void vlist_one_ldouble(va_list *pargs) {
    long double value;

    value = va_arg(*pargs, long double);
    printf(" %Lf", value);
}

void vlist_ldouble(int count, va_list *pargs) {
    printf("Now printing %d long doubles:\n", count);

    for (int i=0; i<count; i++)
        vlist_one_ldouble(pargs);

    printf("\n");
}

void list_ldouble(int count, ...) {
    va_list args;
    va_start(args, count);

    vlist_ldouble(count, &args);

    va_end(args);
    return;
}

int main(void) {
    long double a = 1.1, b=2.2, c=3.3;

    list_ldouble(3, a, b, c);
    list_ldouble(4, 1.1L, 2.2L, 3.3L, 4.4L);

    return 0;
}

A very important thing which we can't check without your full code is that you have to be sure that you're actually passing long doubles. Here I'm doing it two ways: firstly, with long double variables; secondly with long double literals.

The L suffix here is an example of a "typed literal". When you specify the actual value of a variable in your source code (e.g. 1 or 3.14 or 'b' or "foo") that's a literal. Usually you don't have to specify explicitly what the type of the literal is, as the compiler can do any necessary conversions. For floating point literals, the standard says this:

An unsuffixed floating constant has type double. If suffixed by the letter f or F, it has type float. If suffixed by the letter l or L, it has type long double.

If a standard function with a prototype is called with a literal value, say func(1.2) then the compiler will know whether func() is expecting a float, a double or a long double and pass the appropriate type to the function. But with variable length argument list functions, there's no prototype to work from, so the compiler works from the type of the value you're passing. If you're passing 1.2, it will pass it as a double. If you want to pass a long double, you have to specify that your 1.2 is actually a long double, and that's what the L is for. If you end up passing a double, but expecting a long double, you'll not get the value you're expecting.

I can't find an ideal reference for this, but this page isn't bad, and these pages are good, but are about C++, rather than C.

If this doesn't answer your problem, post a complete, compilable program which isn't doing what you expect.

Tim
  • 9,171
  • 33
  • 51
  • Thanks a lot for your help. The problem did not come from the pointer to the va_list. Actually I was trying to extract a long double but the data stored was not a long double. I think this is a stupid mistake. The encoding format is not the same between float, double and long double numbers and I suppose it's the reason why I had a problem. Thanks again, you have helped me a lot. PS: I did not know about the kind of conversion you used in your code. Is it like a cast? And I am sorry but I did not want to post all my code because it's a little bit long to read. – CodeKiwi Nov 23 '18 at 19:02
  • I'm glad that sorted it. I had a hunch that was the problem, because the way in which parameters are processed internally in variable length argument lists depends critically on their type, so a type mismatch can cause problems. The `L` I used isn't a cast, it's a "typed literal" I'll add some additional details to the answer to describe what they are. – Tim Nov 23 '18 at 19:21
  • 1
    The code as currently posted is incorrect. Passing a `va_list` argument to a function more than once violates [**7.16 Variable arguments **, paragraph 3 of the C Standard](https://port70.net/~nsz/c/c11/n1570.html#7.16p3) "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 ...**" – Andrew Henle Nov 23 '18 at 21:44
  • @AndrewHenle I don't read that as meaning the code above is incorrect. You omit the rest of that sentence: "and shall be passed to the va_end macro prior to any further reference to ap." My understanding is that this is saying that you can't use the value of type `va_list` in both the called and the calling function (here `vlist_ldouble()` and `list_ldouble()`. This makes complete sense if the value is implemented as a struct, and is passed by value. In the case above, the only use after it is passed out of `list_ldouble` is in `va_end` which is exactly what is required by the standard. – Tim Nov 23 '18 at 22:54
  • @AndrewHenle Ah, but applying the same logic I shouldn't be repeatedly calling `vlist_one_ldouble()` with the `args` value in `vlist_ldouble`, even though that's not explicitly ruled out by the standard. So, yes, the above code is illustrative, and you shouldn't rely on it to work in all situations. – Tim Nov 23 '18 at 23:27
  • 1
    @Tim The C standard states that the value of the `va_list` value `ap` is "indeterminate" if a called function accesses `ap` via `va_arg`. There's no wiggle room there. [Footnote 253](https://port70.net/~nsz/c/c11/n1570.html#note253) states: "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." Thus, it is **not** permitted to "make further use of the original list after the other function returns" if you pass by value as your code does. – Andrew Henle Nov 24 '18 at 00:27
  • @AndrewHenle I think you may have missed the point that I'm largely agreeing with you, that this code shouldn't be used in practice, and that there are better ways of doing this. At the same time, this usage is not "explicitly ruled out by the standard" because a) the function which declares the object of type `va_list` is `list_ldouble` b) this object is passed as an argument to another function `vlist_ldouble` c) that function *doesn't* invoke the `va_arg` macro (instead, it calls yet another function `vlist_one_ldouble` which does). All I'm saying is that the standard could be even clearer. – Tim Nov 24 '18 at 13:03
  • @AndrewHenle So, I've fixed the code in the answer to avoid the problem you noted, while still answering the question. It's better code for that. Thank you. – Tim Nov 24 '18 at 13:14