C++ 在多个函数中使用va_列表时出现意外行为

C++ 在多个函数中使用va_列表时出现意外行为,c++,c,C++,C,在我的项目中,我创建了一个类似于但不同于ANSIC中Axel Tobias Schreiner面向对象编程的C类层次结构。比如说 我初始化的对象与Axel有点不同。当我在多个初始化函数之间传递va_list对象时,我遇到了麻烦。假设我有一个objecttwo,它来自objectone。然后在初始化一个two对象时,我需要先初始化one部分,然后初始化two部分。 所以我有一个init函数,它调用公共初始化器函数,参数初始化two对象的one部分,参数只初始化扩展one对象的two部分 我正在创建

在我的项目中,我创建了一个类似于但不同于ANSIC中Axel Tobias Schreiner面向对象编程的C类层次结构。比如说

我初始化的对象与Axel有点不同。当我在多个初始化函数之间传递va_list对象时,我遇到了麻烦。假设我有一个objecttwo,它来自objectone。然后在初始化一个two对象时,我需要先初始化one部分,然后初始化two部分。 所以我有一个init函数,它调用公共初始化器函数,参数初始化two对象的one部分,参数只初始化扩展one对象的two部分

我正在创建的库相当大,但我从中提取了一个小型项目,演示了相同的问题:

CMakeLists.txt:

cmake_minimum_required(VERSION 3.5)

project (var_args
        VERSION     "0.0"
        LANGUAGES   C CXX
        )

set(HEADERS "init.h")
set(SOURCES init.c program.c)

add_executable(program ${SOURCES} ${HEADERS})

if (NOT MSVC)
    target_compile_options(program PRIVATE -W -Wall -Wextra -pedantic)
else()
    target_compile_options(program PRIVATE /W4)
endif()
初始h:

typedef struct _one {
    int a;
    const char* msg;
} one_t;

/* this struct "derives" from struct _one */
typedef struct _two {
    one_t   parent;
    double  pi;
    double  e;
}two_t;

enum status_t {
    STATUS_SUCCES,
    STATUS_INVALID_ARGUMENT,
    STATUS_ERROR
};

enum init_one_flags {
    INIT_ONE_A,         // 2nd argument should be of type int
    INIT_ONE_MSG,       // 3rd argument should be const char*
    INIT_ONE_FINISHED,  // takes no arugment, but rather tell init1 should be finished.
    INIT_ONE_SENTINAL   // Keep at the last position.
};

enum init_two_flags {
    INIT_TWO_PI = INIT_ONE_SENTINAL,    // 2nd arugument should be of type double.
    INIT_TWO_E,                         // 2nd argument shoudl be double.
    INIT_TWO_FINISHED,                  // takes no arugment, but rather tell init2 should be finished.
    INIT_TWO_SENTINAL,                  // for init3...
};

#ifdef __cplusplus
extern "C" {
#endif

int init_two(two_t* two, ...);

//void init_one(one_t* one, ...);

#ifdef __cplusplus
}
#endif
初始c:

#include <stdarg.h>
#include "init.h"

static int priv_init1(one_t* one, va_list list)
{
    // use default values;
    int a = 0;
    const char* msg = "";

    int selector, ret = STATUS_SUCCES;

    while ((selector = va_arg(list, int)) != INIT_ONE_FINISHED) {
        switch (selector) {
        case INIT_ONE_A:
            a = va_arg(list, int);
            break;
        case INIT_ONE_MSG:
            msg = va_arg(list, const char*);
            break;
        default:
            // unknown argument
            return STATUS_INVALID_ARGUMENT;
        }
    }

    one->a = a;
    one->msg = msg;
    return ret;
}

static int priv_init2(two_t* two, va_list list)
{
    double pi = 3.1415, e=2.7128;
    int selector, ret = STATUS_SUCCES;

    ret = priv_init1((one_t*)two, list);
    if (ret)
        return ret;

    while ((selector = va_arg(list, int)) != INIT_TWO_FINISHED) {
        switch (selector) {
        case INIT_TWO_PI:
            pi = va_arg(list, double);
            break;
        case INIT_TWO_E:
            pi = va_arg(list, double);
            break;
        default:
            return STATUS_INVALID_ARGUMENT;
        }
    }

    two->pi = pi;
    two->e = e;

    return STATUS_SUCCES;
}

int init_two(two_t* two, ...)
{
    int ret;
    va_list list;
    va_start(list, two);
    ret = priv_init2(two, list);
    va_end(list);

    return ret;
}
#包括
#包括“init.h”
静态int priv_init1(一个,一个,一个列表)
{
//使用默认值;
int a=0;
const char*msg=“”;
int选择器,ret=状态\成功;
while((选择器=va_arg(list,int))!=INIT_ONE_FINISHED){
开关(选择器){
案例一开始:
a=va_arg(列表,整数);
打破
案例初始信息:
msg=va_arg(列表,常量字符*);
打破
违约:
//未知数
返回状态\u无效\u参数;
}
}
一->a=a;
一->味精=味精;
返回ret;
}
静态int priv_init2(两个t*2,va_列表)
{
双pi=3.1415,e=2.7128;
int选择器,ret=状态\成功;
ret=priv_init1((一个*)二,列表);
如果(ret)
返回ret;
while((选择器=va_arg(list,int))!=INIT_TWO_FINISHED){
开关(选择器){
案例初始二π:
pi=va_arg(列表,双精度);
打破
第一种情况是:
pi=va_arg(列表,双精度);
打破
违约:
返回状态\u无效\u参数;
}
}
二->π=π;
二->e=e;
返回状态\u成功;
}
int init_two(two_t*two,…)
{
int ret;
va_列表;
va_启动(列表,两个);
ret=priv_init2(两个,列表);
va_end(列表);
返回ret;
}
方案c:

#include <stdio.h>
#include "init.h"

int main() {
    int ret;
    two_t two;

    ret = init_two(
        &two,
        INIT_ONE_A,         1,
        INIT_ONE_MSG,       "Hello, world",
        INIT_ONE_FINISHED,
        INIT_TWO_PI,        2 * 3.1415,
        INIT_TWO_FINISHED
    );

    if (ret) {
        fprintf(stderr, "unable to init two...\n");
        printf("a=%d\tmsg=%s\tpi=%lf\te%lf\n",
            two.parent.a,
            two.parent.msg,
            two.pi,
            two.e
        );
        return 1;
    }
    else {
        printf("a=%d\tmsg=%s\tpi=%lf\te%lf\n",
            two.parent.a,
            two.parent.msg,
            two.pi,
            two.e
        );
        return 0;
    }
}
#包括
#包括“init.h”
int main(){
int ret;
二对二,;
ret=init_二(
&二,,
一开始,
INIT_ONE_MSG,“你好,世界”,
一开始就完成了,
初始二π,2*3.1415,
一次两次完成
);
如果(ret){
fprintf(stderr,“无法初始化两个…\n”);
printf(“a=%d\tmsg=%s\tpi=%lf\te%lf\n”,
二、家长a、,
two.parent.msg,
2.pi,
二、e
);
返回1;
}
否则{
printf(“a=%d\tmsg=%s\tpi=%lf\te%lf\n”,
二、家长a、,
two.parent.msg,
2.pi,
二、e
);
返回0;
}
}
现在我遇到的问题是,这段代码的行为正是我在Linux上使用gcc或clang调试和发布版本时所期望的。不幸的是,该代码在使用visual studio 17的Windows上失败

所以程序的输出应该是这样的:

a=1 msg=Hello,世界pi=6.283000 e2.712800

这正是我使用gcc(5.4.0-6)在Linux上获得的

在windows上,我得到:

a=1 msg=Hello,world pi=jiberish here e2=jiberish here

函数
init_two
返回函数在Linux上成功,而在windows上不成功。我还可以看到,一部分二部分的初始化成功,而两部分没有。 如果有人能指出问题的症结所在,我将不胜感激。是否在Linux上通过引用传递va_列表,而在windows上通过值传递va_列表?枚举值在Linux上是否提升为int,而在windows上则作为char传递?
最后,我把问题标记为C++和C++,因为我知道我演示的代码是C,但是我希望它也能与C++编译器一起工作。

< P>编译器的VistaValueSt//Cord>的实现在编译器之间可以有很大的不同。p> 例如,gcc可能将其实现为指针,因此通过值将其传递给另一个函数仍然会修改底层结构,以便调用函数中的更改在调用函数中可见,而MSVC将其实现为结构,因此调用函数中的更改在调用方中不可见

您可以通过将指向初始
va_列表的指针传递给所有需要它的函数来解决这个问题。然后,内部状态将在所有函数中保持一致

// use pointer to va_list
static int priv_init1(one_t* one, va_list *list)
{
    // use default values;
    int a = 0;
    const char* msg = "";

    int selector, ret = STATUS_SUCCES;

    while ((selector = va_arg(*list, int)) != INIT_ONE_FINISHED) {
        switch (selector) {
        case INIT_ONE_A:
            a = va_arg(*list, int);
            break;
        case INIT_ONE_MSG:
            msg = va_arg(*list, const char*);
            break;
        default:
            // unknown argument
            return STATUS_INVALID_ARGUMENT;
        }
    }

    one->a = a;
    one->msg = msg;

    return ret;
}

// use pointer to va_list
static int priv_init2(two_t* two, va_list *list)
{
    double pi = 3.1415, e=2.7128;
    int selector, ret = STATUS_SUCCES;

    ret = priv_init1((one_t*)two, list);
    if (ret)
        return ret;

    while ((selector = va_arg(*list, int)) != INIT_TWO_FINISHED) {
        switch (selector) {
        case INIT_TWO_PI:
            pi = va_arg(*list, double);
            break;
        case INIT_TWO_E:
            pi = va_arg(*list, double);
            break;
        default:
            return STATUS_INVALID_ARGUMENT;
        }
    }

    two->pi = pi;
    two->e = e;

    return STATUS_SUCCES;
}

int init_two(two_t* two, ...)
{
    int ret;
    va_list list;
    va_start(list, two);
    ret = priv_init2(two, &list);    // pass pointer
    va_end(list);

    return ret;
}
本手册第7.16p3节明确提到了这种用法:

声明的类型为

va_list
哪一种是适合容纳的完整对象类型 宏
va_start
va_arg
va_end
所需的信息,以及
va_copy
。如果需要访问不同的参数,则 被调用函数应声明一个对象(通常称为 作为本子条款中的
ap
),具有类型
va_列表
。 对象
ap
可以作为参数传递给另一个对象 功能;如果该函数调用带有 参数
ap
,调用函数中
ap
的值为 不确定,应在 任何对
ap
的进一步引用。(253)

253)允许创建指向
va的指针_
static int priv_init1(one_t* one, va_list* list)
{
    // use default values;
    int a = 0;
    const char* msg = "";

    int selector, ret = STATUS_SUCCES;

    while ((selector = va_arg(*list, int)) != INIT_ONE_FINISHED) {
        switch (selector) {
        case INIT_ONE_A:
            a = va_arg(*list, int);
            break;
        case INIT_ONE_MSG:
            msg = va_arg(*list, const char*);
            break;
        default:
            // unknown argument
            return STATUS_INVALID_ARGUMENT;
        }
    }

    one->a = a;
    one->msg = msg;
    return ret;
}

static int priv_init2(two_t* two, va_list list)
{
    double pi = 3.1415, e=2.7128;
    int selector, ret = STATUS_SUCCES;

    ret = priv_init1((one_t*)two, &list);
    if (ret)
        return ret;

    while ((selector = va_arg(list, int)) != INIT_TWO_FINISHED) {
        switch (selector) {
        case INIT_TWO_PI:
            pi = va_arg(list, double);
            break;
        case INIT_TWO_E:
            pi = va_arg(list, double);
            break;
        default:
            return STATUS_INVALID_ARGUMENT;
        }
    }

    two->pi = pi;
    two->e = e;

    return STATUS_SUCCES;
}
static int priv_init1(one_t* one, va_list *list) {
    while ((selector = va_arg((*list), int)) != INIT_ONE_FINISHED) {
        // ...
    }

    // ...
    return ret;
}

static int priv_init2(two_t* two, va_list *list) {
    int ret = priv_init1((one_t*)two, list);

    // ...
    while ((selector = va_arg((*list), int)) != INIT_TWO_FINISHED) {
        // ...
    }

    // ...    
    return STATUS_SUCCES;
}

int init_two(two_t* two, ...) {
    int ret;
    va_list list;

    va_start(list, two);
    ret = priv_init2(two, &list);
    va_end(list);

    return ret;
}