Platform inconsistencies with vsprintf and va_list

走远了吗. 提交于 2019-12-07 11:21:14

问题


Background: I am currently trying to "extend" standard C formatting with support for handling a certain struct, similar to how Objective-C extends C formatting to allow support for NSString with the "%@" sequence.

The one problem I'm struggling with is that vsprintf seems to be behaving differently on OS X versus Linux (I've tested with Ubuntu 10.10 and 12.04). On OS X, it is behaving how I thought it should, where after calling vsprintf, calling va_arg returns the ms pointer (as if the vsprintf function called va_arg to get the 5). On Linux, however, the va_list does not change from vsprintf, and calling va_arg returns 5.

I would really like to figure out a way to implement this functionality so that it behaves consistently across platforms. Is it wrong to assume that you can expect vsprintf to consistently change the pointer inside va_list so that the next time you call va_arg it returns the next not-yet-used argument?

I have simplified my code as much as possible to demonstrates the issue. On OS X, this code prints the correct address of the pointer returned from malloc. On Linux, the value of ms in foo becomes 5, so it prints 5.

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

static void foo(void *, ...);

typedef struct {
    char *value;
} mystruct;

int main(int argc, char *argv[]) {
    mystruct *ms = malloc(sizeof(mystruct));
    foo(NULL, "%d %@", 5, ms);
}

void foo(void *dummy, ...) {
    va_list args;
    va_start(args, dummy);
    char buffer[512];
    int buffer_ptr = 0;
    int i = 0;
    char *format = va_arg(args, char *);

    buffer[0] = '\0';

    for (i = 0; i < strlen(format); i++) {
        if (i <= strlen(format) - 1 && (format[i] == '%' && format[i+1] == '@')) {
            vsprintf(buffer, buffer, args);

            /* can expect the next argument to be a mystruct pointer */
            mystruct *ms = va_arg(args, mystruct *);
            buffer[buffer_ptr+1] = '\0';
            fprintf(stderr, "%p", ms); /* SHOULD NOT PRINT 5 */

            /* concatenate here */  
        } else {
            buffer[buffer_ptr++] = format[i];
            buffer[buffer_ptr] = '\0';
        }
    }

    va_end(args);
}

回答1:


You need to use va_copy if you're going to use an argument list more than once -- failure to do so is undefined behavior. Your code should look something like this:

va_list args;
va_start(args, dummy);
...
char *format = va_arg(args, char *);
...
va_list argsCopy;
va_copy(argsCopy, args);
vsprintf(..., argsCopy);
va_end(argsCopy);
...
mystruct *ms = va_arg(args, mystruct *);
...
va_end(args);



回答2:


The problem is that it's up to the implementation how to implement a va_list -- it might contain all the info and state for extracting arguments directly, or it might contain a pointer to something that holds the state indirectly. So passing it to vsprintf might make a copy of all the relevant state or it might not.

What you want for what you are trying to do is a vspintf-like function that takes a va_list * rather than a va_list, so you can ensure you have the proper state after it returns. Unfortunately, the standard does not provide any such function.



来源:https://stackoverflow.com/questions/10807310/platform-inconsistencies-with-vsprintf-and-va-list

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!