C++ 在多个函数中使用va_列表时出现意外行为
在我的项目中,我创建了一个类似于但不同于ANSIC中Axel Tobias Schreiner面向对象编程的C类层次结构。比如说 我初始化的对象与Axel有点不同。当我在多个初始化函数之间传递va_list对象时,我遇到了麻烦。假设我有一个objecttwo,它来自objectone。然后在初始化一个two对象时,我需要先初始化one部分,然后初始化two部分。 所以我有一个init函数,它调用公共初始化器函数,参数初始化two对象的one部分,参数只初始化扩展one对象的two部分 我正在创建的库相当大,但我从中提取了一个小型项目,演示了相同的问题: CMakeLists.txt:C++ 在多个函数中使用va_列表时出现意外行为,c++,c,C++,C,在我的项目中,我创建了一个类似于但不同于ANSIC中Axel Tobias Schreiner面向对象编程的C类层次结构。比如说 我初始化的对象与Axel有点不同。当我在多个初始化函数之间传递va_list对象时,我遇到了麻烦。假设我有一个objecttwo,它来自objectone。然后在初始化一个two对象时,我需要先初始化one部分,然后初始化two部分。 所以我有一个init函数,它调用公共初始化器函数,参数初始化two对象的one部分,参数只初始化扩展one对象的two部分 我正在创建
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;
}