3

Does any of the C Standards describe any kind of a check to ensure that const is used and accessed appropriately with variable argument lists?

If I have the following source for a function with a variable argument list:

int func (char *pBuffer, int nLen, ...)
{
    va_list ArgList;
    va_start(ArgList, nLen);

    char *pName = va_arg(ArgList, char*);

    // do things with the string such as copy it to an output buffer.
    strcpy (pBuffer, pName); // works. source string argument declared const char *, not modified by strcpy()
    *pName = 'x';    // error here running in Debug mode, write access violation.

    va_end(ArgList);

    return 0;
}

And I want to call this function with a const argument as in:

int func2(char *pBuff, int nLen, const char *pName)
{

    return func(pBuff, nLen, pName);
}

I have tried this with Visual Studio 2019 Community Edition and it compiles with no warnings on Warning level 3 using the C11 standard.

When I try to run a Debug compile, I see a write violation exception thrown. When I try a Release compile it runs but the exit code is wrong probably because something important was trampled.

It appears in an AI created source example that the argument pName in the above function, func() could be accessed with const char *p = va_arg(ArgList, const char*); to enforce const however not doing so does not appear to make any difference to the compiler.

So it appears from my testing that using a const variable as a parameter to a function with a variable argument list depends on the called function to be conservative and treat variables as const unless the argument is designated to be non-const by the function documentation.

If the called function does not specify the const modifier in the va_arg() call and the variable definition used with the va_arg() and the called function then tries to modify the variable which may be const then is it Undefined Behavior?

I suppose that vsprintf() takes such a conservative stance with strings and only modifies the output buffer treating the variables in the list of arguments to print as being const?

So it appears that when using variable argument lists a coding policy would be to use const char *p = va_arg(ArgList, const char*); by default unless the argument is explicitly designed to have a modifiable value.

And it doesn't hurt to specify const in other function interfaces even if the leaf function called is using variable arguments. I suppose in many cases function higher up in the call tree may never know the leaf function and whether it uses variable argument list or not.

2
  • but the exit code is wrong probably because something important was trampled I used VS 2022 Community, and in Release mode, I got an exit code that was correct. I am curious as to what your exit code was. Commented May 19 at 7:07
  • @PaulH It was a large negative number. My test harness was a C++ console main that then called the main entry point in a C file that set up a series of char arrays and char pointers to constant strings then exercised the functions. So the source was not all in a single file and there were other C source files as this is a Visual Studio 2019 Community Edition solution with multiple different test and exploratory samples. When I'm investigating some C or C++ question, I just add a new file to the solution that has the source for the investigation and an entry point called from the main. Commented May 19 at 12:01

2 Answers 2

2

And I want to call this function with a const argument as in:…

Your pName argument is not a const argument. It is a (non-const) pointer to a const char. That distinction is important when dealing with const.

It is defined behavior for a const char * argument to be fetched with va_arg using char * per C 2024 7.16.2.2:

… If type [the type passed to va_arg] is not compatible with the type of the actual next argument…, the behavior is undefined, except for the following cases:

— both types are pointers to qualified or unqualified versions of compatible types;…

char is compatible with char, so both const char * and char * are pointers to qualified or unqualified versions of compatible types. (Further, 6.2.5 tells us “…pointers to qualified or unqualified versions of compatible types shall have the same representation…”, and a footnote tells us this is meant to imply interchangeability.)

If the called function does not specify the const modifier in the va_arg() call and the variable definition used with the va_arg() and the called function then tries to modify the variable which may be const then is it Undefined Behavior?

With regard to const, what determines whether attempting to modify an object has undefined behavior or not is how the object was defined. That is, it does not matter whether the argument passed to the function pointed to a const type. It matters whether the actual object pointed to was defined with const. 6.7.4.1 says:

… If an attempt is made to modify an object defined with a const-qualified type through use of an lvalue with non-const-qualified type, the behavior is undefined…

For example, if we have:

char Buffer[1024];
func2(whatever, whatever, Buffer);

then func2 receives Buffer for pName and passes this pName of type const char * to func, which then takes it as a char * and attempts to modify it. That modification is allowed, because Buffer was not defined with const. The fact that pName was const char * is irrelevant.

I suppose that vsprintf() takes such a conservative stance with strings and only modifies the output buffer treating the variables in the list of arguments to print as being const?

vsprintf attempts to do what you tell it to do. It attempts to write to the buffer you pass it, and it attempts to write to an object you pass it for the n conversion (which reports how many characters have been written by vsprintf so far), and it does not attempt to write to anything else except its own workspace (its own automatic variables and such).

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

3 Comments

You've made good, lawyerly points that I needed to understand. When I read const char *p I interpret that as pointer to a string that shouldn't be modified. I use const to tell the reader, "don't modify either this object or the object pointed to in the case of a pointer", and to tell the compiler, "if this is a target of a modification or possible modification then it's an error so let me know". In the case of function argument, it's a note to reader and compiler "this function doesn't modify this argument". I think part of my confusion was seeing a qualifier used with va_arg(). ...
You post has raised another question in my head, what are the variable type promotion rules used by variable argument lists? If I call a function with a char, is it promoted to an int as part of the calling interface? Google AI says there is only integer promotion ("Integer types smaller than int ... are promoted to int" or to unsigned int) and float promotion ("float arguments are promoted to double") and that "No other implicit conversions are performed".
Oh, here's a question in SO about argument promotion. better understanding type promotion of variadic parameters in c
0

If you want the compiler to check for const variables you need to define them as const. Just change the line:

char *pName = va_arg(ArgList, char*);

into

const char *pName = va_arg(ArgList, const char *);

(the second parameter of the va_arg() macro doesn't need to be const, as the result value of va_arg() will be converted to a const char * automatically by the compiler and you aren't using the value elsewhere but in pName. Anyway the code above is clearer.

BTW, there's no way for the compiler to check what types of parameters you are passing to the function, as there's no specification of the kinds and types of the parameters, after the last fixed one. The only thing you have is that shorts and chars are converted to int and that floats are converted to double, before passing them to the function. If you want the compiler to check the parameter, then use a prototype of the function that explicitly includes a type definition for it.

int func(char *pBuff, int nLen, const char *pName);

In a variadic function, it is the caller of the function which decides what parameters (and the types of them) you are going to pass. So there's no other source of information the compiler can use to check parameter types when you call the function (this is done at compilation time) if you don't provide a complete prototype.

It is worse because a variadic function can be called with a type sequence of parameters in one call, and a completely different one in the next time you call it. The compiler has to accept that, because you specified it that way. That's a flaw in the type system, but it is known and there's nothing you can do (well, you can use a complete prototype instead before you call it, as in (I open a new block of scope to hide any other definition visible before the change):

{   /* new block to hide the variadic definition of func */
    int func(char *pBuff, int nLen, const char *pName);
    int n = func(buffer, sizeof buffer, "Se esta quemando la serreria\n");
}

But that is useless as a non-const pointer is converted to const automatically with no code generation (only the compiler know what is const and what is not) the code will be incorrect if you don't convert the next parameter to const char * inside the function.

1 Comment

That is an interesting tip to redeclare a variadic function. I would expect to see a compiler warning from that though. I'll have to try that. And I'm curious if something similar could be done with a function with 'void *' arguments such as the C library function for binary search within a particular source file.

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.