我有一个带void**
参数的函数和一个表示其数据类型
void foo (void** values, int datatype)
在函数内部,根据数据类型,我以这种方式对其进行malloc:
if (datatype == 1)
*values = (int*) malloc (5 * sizeof(int));
else if (datatype == 2)
*values = (float*) malloc (5 * sizeof(float));
现在一切都很好。然而,当字符串进入图片时,事情变得复杂。 void**
需要void***
,因为我需要执行以下操作:
*values = (char**) malloc (5 * sizeof(char*));
for(i=0;i<5;i++)
(*values)[i] = (char*) malloc (10);
..
strncpy( (*values)[0], "hello", 5);
如何处理这种情况?
我可以将char***
传递给期望void**
但在其中正确投射的函数吗?
void foo (void** values, int datatype) {
if(datatype == 3) {
char*** tmp_vals = (char***) values;
*tmp_vals = (char**) malloc (5 * sizeof(char*));
...
(*tmp_vals)[i] = (char*) malloc (10 * sizeof(char));
strncpy ( (*tmp_vals)[i], "hello", 5);
}
所以我只是将void**
投射到char***
。我尝试了这个并忽略了警告,它运行正常。
但这样安全吗?有更优雅的选择吗?
答案 0 :(得分:7)
如何处理这种情况?我可以将
char***
传递给期望void**
但在其中正确投射的函数吗?
不,这是技术上未定义的行为。它似乎可以在您的计算机上运行,但在未来的某台计算机上可能会失败,这些计算机实现了具有不同表示形式的不同指针类型,这是C语言标准所允许的。
如果您的函数需要void**
,那么最好将void**
传递给它。任何指针类型都可以隐式转换为void*
,但只能在顶层运行:char*
可以转换为void*
,char**
可以隐式转换为{ {1}}(因为void*
是“指向char**
”的指针),但char*
无法转换为char**
,同样{{} 1}} 无法转换为void**
。
调用此函数的正确方法是将其传递给适当的char***
,然后将生成的void**
指针强制转换回其原始类型:
void**
假设void*
实际上指向void foo(void **values, int datatype)
{
if(datatype == 3)
{
char ***str_values = ...;
*values = str_values; // Implicit cast from char*** to void*
}
else
...
}
...
void *values;
foo(&values, 2);
char ***real_values = (char ***)values;
,则此强制转换有效,并且在任何代码路径中都没有任何未定义的行为。
答案 1 :(得分:5)
void *
只是指向未指定类型的指针;它可以是指向int
,char
或char *
,或char **
或任何您想要的任何内容的指针,只要您确定取消引用,您将其视为适当的类型(或原始类型可以安全地解释为的类型)。
因此,void **
只是指向void *
的指针,它可以是指向任何类型的指针,例如char *
。所以,是的,如果你要分配某些类型的对象的数组,并且在一种情况下这些对象是char *
,那么你可以使用void **
来引用它们,给你一些可以被引用的东西作为char ***
。
直接看到这种结构通常很常见,因为通常你会将一些类型或长度的信息附加到数组中,而不是char ***
你有一个struct typed_object **foo
或类似的东西struct typed_object
1}}有一个类型标记和指针,你将从这些元素中提取的指针强制转换为适当的类型,或者你有一个struct typed_array *foo
,它是一个包含类型和数组的结构。
关于风格的几点注释。首先,做这种事情可能会使您的代码难以阅读。要非常小心地构建它并将其清楚地记录下来,以便人们(包括你自己)可以弄清楚发生了什么。另外,不要投射malloc
的结果; void *
自动升级到其分配的类型,如果您忘记包含malloc
或更新类型声明但忘记更新,则转换<stdlib.h>
的结果会导致细微错误演员。有关详细信息,请参阅this question。
将声明中的*
附加到变量名称而不是类型名称通常是一个好习惯,就像它实际解析的那样。以下内容声明了一个char
和一个char *
,但如果您按照编写它们的方式编写它,您可能会期望它声明两个char *
:
char *foo, bar;
或者写另一种方式:
char* foo, bar;
答案 2 :(得分:3)
您根本不需要(也可能不应该)使用void **
- 只需使用常规void *
即可。根据C11 6.3.2.3.1,“指向void
的指针可以转换为指向任何对象类型的指针。指向任何对象类型的指针可以转换为指向void
的指针并返回再次;结果将等于原始指针。“指针变量(包括指向另一个指针的指针)是一个对象。 void **
不是“指向void
的指针”。您可以自由安全地转换为void *
,但不能保证能够安全地转换为void **
。
所以你可以这样做:
void foo (void* values, int datatype) {
if ( datatype == 1 ) {
int ** pnvalues = values;
*pnvalues = malloc(5 * sizeof int);
/* Rest of function */
}
依此类推,然后将其称为:
int * new_int_array;
foo(&new_int_array, 1);
&new_int_array
的类型为int **
,它将void *
隐式转换为foo()
,foo()
会将其转换为int **
类型并取消引用它以间接修改new_int_array
以指向它已动态分配的新内存。
对于指向动态字符串数组的指针:
void foo (void* values, int datatype) {
/* Deal with previous datatypes */
} else if ( datatype == 3 ) {
char *** psvalues = values;
*psvalues = malloc(5 * sizeof char *);
*psvalues[0] = malloc(5);
/* Rest of function */
}
依此类推,并称之为:
char ** new_string_array;
foo(&new_string_array, 3);
同样,&new_string_array
类型为char ***
,再次隐式转换为void *
,foo()
将其转换回来,间接使new_string_array
指向新的分配的内存块。
答案 3 :(得分:1)
有一个内置机制可以做到这一点,还有额外的好处,它允许可变数量的参数。通常以这种格式yourfunc(char * format_string,...)
/*_Just for reference_ the functions required for variable arguments can be defined as:
#define va_list char*
#define va_arg(ap,type) (*(type *)(((ap)+=(((sizeof(type))+(sizeof(int)-1)) \
& (~(sizeof(int)-1))))-(((sizeof(type))+ \
(sizeof(int)-1)) & (~(sizeof(int)-1)))))
#define va_end(ap) (void) 0
#define va_start(ap,arg) (void)((ap)=(((char *)&(arg))+(((sizeof(arg))+ \
(sizeof(int)-1)) & (~(sizeof(int)-1)))))
*/
所以这是一个基本的例子,你可以使用格式字符串和可变数量的args
#define INT '0'
#define DOUBLE '1'
#define STRING '2'
void yourfunc(char *fmt_string, ...){
va_list args;
va_start (args, fmt_string);
while(*fmt_string){
switch(*fmt_string++){
case INT: some_intfxn(va_arg(ap, int));
case DOUBLE: some_doublefxn(va_arg(ap, double));
case STRING: some_stringfxn(va_arg(ap, char *));
/* extend this as you like using pointers and casting to your type */
default: handlfailfunc();
}
}
va_end (args);
}
因此您可以将其运行为:yourfunc("0122",42,3.14159,"hello","world");
或者因为你只想要1开始yourfunc("1",2.17);
它没有比这更通用。您甚至可以设置多个整数类型,以告诉它在该特定整数上运行不同的函数集。如果format_string太繁琐了,那么就可以轻松地使用int datatype
,但是你只能使用1个arg(技术上你可以将位操作用于OR数据类型| num_args但我离题了)
以下是一种类型的值形式:
#define INT '0'
#define DOUBLE '1'
#define STRING '2'
void yourfunc(datatype, ...){ /*leaving "..." for future while on datatype(s)*/
va_list args;
va_start (args, datatype);
switch(datatype){
case INT: some_intfxn(va_arg(ap, int));
case DOUBLE: some_doublefxn(va_arg(ap, double));
case STRING: some_stringfxn(va_arg(ap, char *));
/* extend this as you like using pointers and casting to your type */
default: handlfailfunc();
}
va_end (args);
}
答案 4 :(得分:0)
有了一些技巧,你可以做到。见例:
int sizes[] = { 0, sizeof(int), sizeof(float), sizeof(char *) }
void *foo(datatype) {
void *rc = (void*)malloc(5 * sizes[datatype]);
switch(datatype) {
case 1: {
int *p_int = (int*)rc;
for(int i = 0; i < 5; i++)
p_int[i] = 1;
} break;
case 3: {
char **p_ch = (char**)rc;
for(int i = 0; i < 5; i++)
p_ch[i] = strdup("hello");
} break;
} // switch
return rc;
} // foo
在调用者中,只需将返回值强制转换为适当的指针,然后使用它。