How to properly write asnprintf without format string compiler warnings? - c

I'd like to write a asnprintf function -- which is a wrapper around snprintf, but it mallocs the string according to its output size. Unfortunately when I compile I get a warning (promoted to error on my system) format string is not a string literal [-Werror,-Wformat-nonliteral].
I looked up the warning and apparently there are security concerns with passing a non-literal to printf functions, but in my case, I need to take in a format pointer, and pass that on.
Is there a good way around this that does not expose the same security vulnerability?
My function as is is as follows:
int
asnprintf(char **strp, int max_len, const char *fmt, ...)
{
int len;
va_list ap,ap2;
va_start(ap, fmt);
va_copy(ap2, ap);
len = vsnprintf(NULL, 0, fmt, ap);
if ( len > max_len)
len = max_len;
*strp = malloc(len+1);
if (*strp == NULL)
return -1;
len = vsnprintf(*strp, len+1, fmt, ap2);
va_end(ap2);
va_end(ap);
return len;
}

If you are targeting only GCC and Clang as compilers, you can get around this pretty easily by temporarily disabling the warning for that specific function:
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#pragma GCC diagnostic ignored "-Wformat-security"
// Function definition here...
#pragma GCC diagnostic pop
Clang should recognize #pragma GCC too. You may also need to ignore -Wformat-security as I did above depending on your compiler flags.
Godbolt link to working example with Clang 11.
I'm wondering if it's possible for example to require that my function take only string literals
As Craig Estey suggests above, you could make use of the format function attribute to make the compiler perform this check for you:
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#pragma GCC diagnostic ignored "-Wformat-security"
int __attribute__((format(printf, 3, 4))) asnprintf(char **strp, int max_len, const char *fmt, ...) {
// ... implementation ...
}
#pragma GCC diagnostic pop
char global_fmt[100];
int main(void) {
char *res;
asnprintf(&res, 100, "asd"); // will compile
asnprintf(&res, 100, global_fmt); // will NOT compile
return 0;
}
You could also do this, with a bit of trickery, using a macro and some compiler built-ins:
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#pragma GCC diagnostic ignored "-Wformat-security"
int internal_asnprintf(char **strp, int max_len, const char *fmt, ...) {
return printf(fmt);
}
#pragma GCC diagnostic pop
#define asnprintf(strp, maxlen, fmt, ...) ({ \
_Static_assert(__builtin_constant_p(fmt), "format string is not a constant"); \
internal_asnprintf(strp, maxlen, fmt, __VA_ARGS__); \
})
char global_fmt[100];
int main(void) {
char *res;
asnprintf(&res, 100, "asd"); // will compile
asnprintf(&res, 100, global_fmt); // will NOT compile
return 0;
}
Note that the above code makes use of statement expressions (({...})) which are a non-standard extension and may or may not be available depending on your compiler flags.

From my top comments ...
Just add __attribute__((__format__(__printf__,3,4))) to your asnprintf declaration and/or definition.
This will cue the compiler to not complain.
And, the further benefit is that it will check the variadic arguments passed to asnprintf against the format string.
So:
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
// put this in a .h file!?
int __attribute__((__format__(__printf__,3,4)))
asnprintf(char **strp, int max_len, const char *fmt, ...);
int
asnprintf(char **strp, int max_len, const char *fmt, ...)
{
int len;
va_list ap, ap2;
va_start(ap, fmt);
va_copy(ap2, ap);
len = vsnprintf(NULL, 0, fmt, ap);
if (len > max_len)
len = max_len;
*strp = malloc(len + 1);
if (*strp == NULL)
return -1;
len = vsnprintf(*strp, len + 1, fmt, ap2);
va_end(ap2);
va_end(ap);
return len;
}

Related

Reimplementation of printf() using printf() in C

I'm making a embedded linux project and I want to do a simple debug messages library where I can disable my debug messages (using pre-compilation directives) when my code is in production phase and substitute for some type of log in a database (in the future). I'm having trouble with reimplementation of printf() because of va_list. This is my code until now:
My source file:
#include "debug_msgs.h"
#define ENABLE_DEBUG_MSGS 1U
#if ENABLE_DEBUG_MSGS
int print(const char *fmt, ...) {
int n = -1;
va_list ap;
va_start(ap, fmt);
n = printf(fmt, ap);
va_end(ap);
return n;
}
#else
int print(const char *fmt, ...) {
return 0;
}
#endif
My header file:
#ifndef DEBUG_MSGS_H
#define DEBUG_MSGS_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdint.h>
#include <unistd.h>
int print(const char *fmt, ...);
#endif
My problem is:
My implementation compiles (I know this doesn't mean nothing), but when I do a simple test like:
int num1 = 10;
int num2 = 100;
int num3 = 1000;
float floating = 3.14;
char str[] = {"Please work, please"};
int number=-1;
number = print("\nTesting %d %d %d %f %s\n", num1, num2, num3, floating, str);
printf("\nnumber = %d\n",number); //Just to check the return
I get this output:
Testing -1095005212 100 -1095005212 -0.000002 H*�
number = 52
Complementary informations:
This is my platform info:
Linux mdm9607 3.18.44 #1 PREEMPT Tue Sep 13 19:45:33 UTC 2022 armv7l GNU/Linux
My compiler info:
arm-oe-linux-gnueabi-gcc
My $CFLAGS:
-O2 -fexpensive-optimizations -frename-registers -fomit-frame-pointer -MMD -MP
My $LDFLAGS:
-Wl,-O1 -Wl,--hash-style=gnu -Wl,--as-needed
LDFLAGS += -lumdp -lumdpcommon
LDFLAGS += -lpthread -lxml2 -lrt
LDFLAGS += -lm
Instead of printf, you should be using vprintf or vfprintf with a va_list argument:
#include <stdarg.h>
#include <stdio.h>
int print(const char *fmt, ...) {
int n;
va_list ap;
va_start(ap, fmt);
n = vprintf(fmt, ap);
va_end(ap);
return n;
}
The erroneous code compiles because printf, as a vararg function, can accept any argument types after the format string. Yet you can avoid type mismatches for printf arguments by enabling more compiler warnings: gcc -Wall -Wextra -Werror would at least complain about using printf with a non constant format string and would perform compile time type checking if the format string is a string literal.

How to update a vargs to send to printf

I have the following code where I format a message that has been passed via a logger:
va_list args;
va_start(args, level);
// pop the msg and do some string replaces
char *msg = va_arg(args, char*);
ssize_t len = str_replace(tmp, "%(msg)s", msg, output, 1024);
// now how to get it 'back in' so I can print it?'
// msg = output
vfprintf(stderr, output, args);
The above code prints nothing, as it seems to have 'popped' the msg without me placing the updated msg back in there. What would be a possible way to do this?
Instead of fiddling with the format string, you could print the variadic arguments passed to the log function to a string first, then print that string with a regular fprintf via the %s format.
You haven't posted your function signature, but it looks as if you passed the format string as first variadic argument. That's unusual. I've changed the signature so that the format string is the last non-variadic arg. That's common and it also opens up the possibility to chech your format strings for correctness. (See below.)
Here's an example implementation:
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
enum {
FATAL, ERROR, WARNING, INFO, NLEVEL
};
void say(int level, const char *fmt, ...)
{
static const char *templates[NLEVEL] = {
"\033[1m!!! FATAL: %s !!!\033[0m\n",
"ERROR: %s\n",
"Warning: %s\n",
"%s Just sayin'.\n"
};
const char *tmp;
char output[1024];
va_list args;
// print arguments to temporary string
va_start(args, fmt);
vsnprintf(output, sizeof(output), fmt, args);
va_end(args);
// find and print template string with message
if (level < 0) level = 0;
if (level >= NLEVEL) level = INFO;
tmp = templates[level];
fprintf(stderr, tmp, output);
va_end(args);
if (level == FATAL) exit(1);
}
Use it like this:
int main(int argc, char *argv[])
{
int a = 6;
int b = 7;
double Tmin = -273.15;
say(INFO, "%d * %d == %d.", a, b, a * b);
say(INFO, "The %s is %s.", "apple", "red and juicy");
say(WARNING, "Temperature falls below Tmin (%gC)", Tmin);
say(ERROR, "That should %s have happened.",
(rand() % 2) ? "never" : "not really");
say(FATAL, "Out of coffee!");
return 0;
}
Finally, you can make your logging functions safer and let the compiler check the validity of format strings. GCC and Clang do this with arguments, the Microsoft compiler does it with SAL, the Source-code Annotation Language. Other compilers may have their own method to do such checks. (More likely, they just emulate one of the GC or MS systems.)
With a little macro dance in the header you can cater for both variants:
#if defined(_MSC_VER)
#define PRINTF_FMT _Printf_format_string_
#endif
#if defined(__GNUC__) || defined(__clang__)
#define PRINTF_ARGS(IFMT, IARGS) __attribute__((format(printf, IFMT, IARGS)))
#endif
#ifndef PRINTF_ARGS
#define PRINTF_ARGS(IFMT, IARGS)
#endif
#ifndef PRINTF_FMT
#define PRINTF_FMT
#endif
void say(int level, PRINTF_FMT const char *fmt, ...) PRINTF_ARGS(2, 3);
Now a call like this:
say(WARNING, "Meta: Wrong formt for %d.", ~0ull);
warns you about the wrong format specifier.

Custom print function with _TIME_, _FILE_, _FUNCTION_, _LINE_

I have following code in debug.h:
#ifndef __DEBUG_H__
#define __DEBUG_H__
#ifdef DEBUG
int al_debug(const char *format,
const char * time,
const char * file,
const char * function,
int line,
...);
#define debug(fmt, args...) al_debug(fmt, __TIME__, __FILE__, __FUNCTION__, __LINE__, args...)
#else /* IF DEBUG NOT DEFINED*/
#define debug(fmt, ...) /* DO NOT PRINT ANYTHING IF DEBUG IS NOT PRESENT */
#endif /* END OF DEBUG */
#endif /* END OF __DEBUG_H__ */
In debug.c:
#include <stdarg.h>
#include <string.h>
#define __WRAP_FUNCTION__
#include "debug.h"
#ifdef DEBUG
int debug(const char *format,
const char * time,
const char * file,
const char * function,
int line,
...)
{
int done=0;
va_list arg;
va_start(arg, format);
done = vfprintf(stdout, "%s :%s:%s:%d", time, file, function, line);
done += vfprintf(stdout, format, arg);
va_end(arg);
return done;
}
#endif
And after compiling it I am getting following errors:
gcc -g -Wall -DDEBUG -c debug.c -o d_debug.o
debug.c:16:1: error: expected declaration specifiers or ‘...’ before string constant
debug.c:16:1: error: expected declaration specifiers or ‘...’ before string constant
debug.c:11:5: error: expected declaration specifiers or ‘...’ before ‘__FUNCTION__’
debug.c:16:1: error: expected declaration specifiers or ‘...’ before numeric constant
How I can fix this problem? What is problem here? Thank you in advance.
1) Change function name
// int debug(
int al_debug(
2) Use regular fprintf()
// done = vfprintf(stdout, "%s :%s:%s:%d", time, file, function, line);
done = fprintf(stdout, "%s :%s:%s:%d", time, file, function, line);
3) Start at line. Use parameter before ...
// va_start(arg, format);
va_start(arg, line);
4) Pedantic note: add done check for < 0 after each print.
done = fprintf(stdout, "%s :%s:%s:%d", time, file, function, line);
if (done >= 0) {
int done_former = done
done = vfprintf(stdout, format, arg);
if (done >= 0) done += done_former;
}
va_end(arg);
return done;
5) Change args...) to args) #leeduhem
[Edit] to work with no args after format.
int al_debug(const char * time, const char * file, const char * function,
int line, const char *format, ...);
#define debug(...) al_debug(__TIME__, __FILE__, __func__, __LINE__, __VA_ARGS__)
// #leeduhem for __VA_ARGS__
#else /* IF DEBUG NOT DEFINED*/
#define debug(...) /* DO NOT PRINT ANYTHING IF DEBUG IS NOT PRESENT */
#endif /* END OF DEBUG */
int al_debug(const char * time, const char * file, const char * function,
int line, const char *format, ...) {
int done = 0;
va_list arg;
va_start(arg, format); // change to `format`
...
Fixed version:
debug.h
#ifndef __DEBUG_H__
#define __DEBUG_H__
#ifdef DEBUG
int al_debug(const char *time, const char *file, const char *func, int line, const char * format, ...);
#define debug(...) al_debug(__TIME__, __FILE__, __func__, __LINE__, __VA_ARGS__)
#else /* IF DEBUG NOT DEFINED*/
#define debug(...) /* DO NOT PRINT ANYTHING IF DEBUG IS NOT PRESENT */
#endif /* END OF DEBUG */
#endif /* END OF __DEBUG_H__ */
debug.c
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#define __WRAP_FUNCTION__
#include "debug.h"
#ifdef DEBUG
int al_debug(const char *time, const char *file, const char *func, int line, const char *format, ...)
{
int done=0;
va_list arg;
va_start(arg, format);
done = fprintf(stdout, "%s :%s:%s:%d: ", time, file, func, line);
done += vfprintf(stdout, format, arg);
va_end(arg);
return done;
}
#endif
test.c
#include <stdio.h>
#include <stdlib.h>
#include "debug.h"
void foo(void)
{
debug("debugg...\n");
debug("%s and %d\n", "debug information", 42);
}
int
main(int argc, char *argv[])
{
foo();
exit(EXIT_SUCCESS);
}
Testing:
$ gcc -g -Wall -I. -DDEBUG debug.c test.c
test.c: In function ‘main’:
test.c:12:10: warning: unused parameter ‘argc’ [-Wunused-parameter]
test.c:12:22: warning: unused parameter ‘argv’ [-Wunused-parameter]
$ ./a.out
10:46:40 :test.c:foo:8: debugg...
10:46:40 :test.c:foo:9: debug information and 42
$ gcc -g -Wall -I. debug.c test.c
test.c: In function ‘main’:
test.c:12:10: warning: unused parameter ‘argc’ [-Wunused-parameter]
test.c:12:22: warning: unused parameter ‘argv’ [-Wunused-parameter]
$ ./a.out
$
A few links that may be helpful:
6.20 Macros with a Variable Number of Arguments
6.47 Function Names as Strings

How to redirect yyout to char* buffer in GNU flex?

By default yyout is set to stdout and can be redirected to FILE* stream. Is a way to redirect yyout to char*?
There are a number of ways to do it. If you post a bit of your scanner code where you are trying to use yyout may be I can give you a more specific answer:
Typically it's in your action where you'd do this. So instead of using ECHO or fprintf(yyout... you'd use something else like
<token> { snprintf(buf, sizeof(buf), "%s", yytext); }
earlier you'd have declared:
char buf[BUFSIZE];
addendum I - An alternate approach
There are clever ways of dealing with the situation, but they are prone to maintenance problems in the long run because they are "clever" .. .and the cleverer a solution the shorter its life. (unless the cleverness is well documented with caveats attached)
int yyprintf(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
if ( some_flag & FLAG_OUTFILE ) {
vfprintf(yyout, fmt, ap);
}
else {
sprintf(buf, fmt, ap);
}
va_end(ap);
}
where buf is a global buffer.
However if you want to make things a bit local:
Approach 2: Fine-grain control over where things go and when
You want fine grain control over where things go along the way. Sometimes you want output
to file, other times to a string, and you don't always know which is which and when and where you can use something like this:
int myvprintf(void *here, size_t len, const char *fmt, va_list ap)
__attribute__((format (gnu_printf, 3, 4), nonnull(1, 3))) {
int rv;
if ( len > 0 ) {
rv = vsnprintf((char *), len, fmt, ap);
}
else {
rv = vfprintf((FILE *)here, fmt, ap);
}
return rv;
}
int myprintf(void *here, size_t len, const char *fmt, ... )
__attribute__((format (gnu_printf, 3, 4), nonnull(1, 3))) {
int rv;
va_list ap;
va_start(ap, fmt);
rv = myvprintf(here, len, fmt, ap);
va_end(ap);
return rv;
}
and use myprintf along the way, you will have control over what is here all the time.
Just for Fun
Don't try this at home. But all yy* identifiers and ECHO are not plain variables, they are #define's. So you could do some clever macro rewriting:
For example, if you are using ECHO everywhere then you can redefine it to do whatever you like (just #undef and #define) in the top section:
%{
#undef ECHO
#define ECHO snprintf(buf, sizeof(buf), "%s", yytext)
%}
%%
<token> ECHO;
%%
just hide it all in convoluted headers and do other cleverness that will make debugging a hell later for a programmer you hate. This can have its own rewards and giggles.

Passing variable number of arguments around

Say I have a C function which takes a variable number of arguments: How can I call another function which expects a variable number of arguments from inside of it, passing all the arguments that got into the first function?
Example:
void format_string(char *fmt, ...);
void debug_print(int dbg_lvl, char *fmt, ...) {
format_string(fmt, /* how do I pass all the arguments from '...'? */);
fprintf(stdout, fmt);
}
To pass the ellipses on, you initialize a va_list as usual and simply pass it to your second function. You don't use va_arg(). Specifically;
void format_string(char *fmt,va_list argptr, char *formatted_string);
void debug_print(int dbg_lvl, char *fmt, ...)
{
char formatted_string[MAX_FMT_SIZE];
va_list argptr;
va_start(argptr,fmt);
format_string(fmt, argptr, formatted_string);
va_end(argptr);
fprintf(stdout, "%s",formatted_string);
}
There's no way of calling (eg) printf without knowing how many arguments you're passing to it, unless you want to get into naughty and non-portable tricks.
The generally used solution is to always provide an alternate form of vararg functions, so printf has vprintf which takes a va_list in place of the .... The ... versions are just wrappers around the va_list versions.
Variadic Functions can be dangerous. Here's a safer trick:
void func(type* values) {
while(*values) {
x = *values++;
/* do whatever with x */
}
}
func((type[]){val1,val2,val3,val4,0});
In magnificent C++11 you could use variadic templates:
template <typename... Ts>
void format_string(char *fmt, Ts ... ts) {}
template <typename... Ts>
void debug_print(int dbg_lvl, char *fmt, Ts... ts)
{
format_string(fmt, ts...);
}
Though you can solve passing the formatter by storing it in local buffer first, but that needs stack and can sometime be issue to deal with. I tried following and it seems to work fine.
#include <stdarg.h>
#include <stdio.h>
void print(char const* fmt, ...)
{
va_list arg;
va_start(arg, fmt);
vprintf(fmt, arg);
va_end(arg);
}
void printFormatted(char const* fmt, va_list arg)
{
vprintf(fmt, arg);
}
void showLog(int mdl, char const* type, ...)
{
print("\nMDL: %d, TYPE: %s", mdl, type);
va_list arg;
va_start(arg, type);
char const* fmt = va_arg(arg, char const*);
printFormatted(fmt, arg);
va_end(arg);
}
int main()
{
int x = 3, y = 6;
showLog(1, "INF, ", "Value = %d, %d Looks Good! %s", x, y, "Infact Awesome!!");
showLog(1, "ERR");
}
Hope this helps.
You can try macro also.
#define NONE 0x00
#define DBG 0x1F
#define INFO 0x0F
#define ERR 0x07
#define EMR 0x03
#define CRIT 0x01
#define DEBUG_LEVEL ERR
#define WHERESTR "[FILE : %s, FUNC : %s, LINE : %d]: "
#define WHEREARG __FILE__,__func__,__LINE__
#define DEBUG(...) fprintf(stderr, __VA_ARGS__)
#define DEBUG_PRINT(X, _fmt, ...) if((DEBUG_LEVEL & X) == X) \
DEBUG(WHERESTR _fmt, WHEREARG,__VA_ARGS__)
int main()
{
int x=10;
DEBUG_PRINT(DBG, "i am x %d\n", x);
return 0;
}
You can use inline assembly for the function call. (in this code I assume the arguments are characters).
void format_string(char *fmt, ...);
void debug_print(int dbg_level, int numOfArgs, char *fmt, ...)
{
va_list argumentsToPass;
va_start(argumentsToPass, fmt);
char *list = new char[numOfArgs];
for(int n = 0; n < numOfArgs; n++)
list[n] = va_arg(argumentsToPass, char);
va_end(argumentsToPass);
for(int n = numOfArgs - 1; n >= 0; n--)
{
char next;
next = list[n];
__asm push next;
}
__asm push fmt;
__asm call format_string;
fprintf(stdout, fmt);
}
Ross' solution cleaned-up a bit. Only works if all args are pointers. Also language implementation must support eliding of previous comma if __VA_ARGS__ is empty (both Visual Studio C++ and GCC do).
// pass number of arguments version
#define callVardicMethodSafely(...) {value_t *args[] = {NULL, __VA_ARGS__}; _actualFunction(args+1,sizeof(args) / sizeof(*args) - 1);}
// NULL terminated array version
#define callVardicMethodSafely(...) {value_t *args[] = {NULL, __VA_ARGS__, NULL}; _actualFunction(args+1);}
Short answer
/// logs all messages below this level, level 0 turns off LOG
#ifndef LOG_LEVEL
#define LOG_LEVEL 5 // 0:off, 1:error, 2:warning, 3: info, 4: debug, 5:verbose
#endif
#define _LOG_FORMAT_SHORT(letter, format) "[" #letter "]: " format "\n"
/// short log
#define log_s(level, format, ...) \
if (level <= LOG_LEVEL) \
printf(_LOG_FORMAT_SHORT(level, format), ##__VA_ARGS__)
usage
log_s(1, "fatal error occurred");
log_s(3, "x=%d and name=%s",2, "ali");
output
[1]: fatal error occurred
[3]: x=2 and name=ali
log with file and line number
const char* _getFileName(const char* path)
{
size_t i = 0;
size_t pos = 0;
char* p = (char*)path;
while (*p) {
i++;
if (*p == '/' || *p == '\\') {
pos = i;
}
p++;
}
return path + pos;
}
#define _LOG_FORMAT(letter, format) \
"[" #letter "][%s:%u] %s(): " format "\n", _getFileName(__FILE__), __LINE__, __FUNCTION__
#ifndef LOG_LEVEL
#define LOG_LEVEL 5 // 0:off, 1:error, 2:warning, 3: info, 4: debug, 5:verbose
#endif
/// long log
#define log_l(level, format, ...) \
if (level <= LOG_LEVEL) \
printf(_LOG_FORMAT(level, format), ##__VA_ARGS__)
usage
log_s(1, "fatal error occurred");
log_s(3, "x=%d and name=%s",2, "ali");
output
[1][test.cpp:97] main(): fatal error occurred
[3][test.cpp:98] main(): x=2 and name=ali
custom print function
you can write custom print function and pass ... args to it and it is also possible to combine this with methods above. source from here
int print_custom(const char* format, ...)
{
static char loc_buf[64];
char* temp = loc_buf;
int len;
va_list arg;
va_list copy;
va_start(arg, format);
va_copy(copy, arg);
len = vsnprintf(NULL, 0, format, arg);
va_end(copy);
if (len >= sizeof(loc_buf)) {
temp = (char*)malloc(len + 1);
if (temp == NULL) {
return 0;
}
}
vsnprintf(temp, len + 1, format, arg);
printf(temp); // replace with any print function you want
va_end(arg);
if (len >= sizeof(loc_buf)) {
free(temp);
}
return len;
}
Let's say you have a typical variadic function you've written. Because at least one argument is required before the variadic one ..., you have to always write an extra argument in usage.
Or do you?
If you wrap your variadic function in a macro, you need no preceding arg. Consider this example:
#define LOGI(...)
((void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))
This is obviously far more convenient, since you needn't specify the initial argument every time.
I'm unsure if this works for all compilers, but it has worked so far for me.
void inner_func(int &i)
{
va_list vars;
va_start(vars, i);
int j = va_arg(vars);
va_end(vars); // Generally useless, but should be included.
}
void func(int i, ...)
{
inner_func(i);
}
You can add the ... to inner_func() if you want, but you don't need it. It works because va_start uses the address of the given variable as the start point. In this case, we are giving it a reference to a variable in func(). So it uses that address and reads the variables after that on the stack. The inner_func() function is reading from the stack address of func(). So it only works if both functions use the same stack segment.
The va_start and va_arg macros will generally work if you give them any var as a starting point. So if you want you can pass pointers to other functions and use those too. You can make your own macros easily enough. All the macros do is typecast memory addresses. However making them work for all the compilers and calling conventions is annoying. So it's generally easier to use the ones that come with the compiler.

Resources