13

我有一个函数,它接受一个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);

这种情况应该如何处理?我可以将 a 传递char***给期望 avoid**但在其中正确转换的函数吗?

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***. 我尝试了这个并忽略了警告,它工作正常。但这安全吗?有没有更优雅的选择?

4

5 回答 5

7

这种情况应该如何处理?我可以将 a 传递char***给期望 avoid**但在其中正确转换的函数吗?

不,这在技术上是未定义的行为。它可能看起来在您的计算机上工作,但它可能会在未来的某些计算机上失败,这些计算机实现具有不同表示的不同指针类型,这是 C 语言标准所允许的。

如果您的函数需要 a void**,那么您最好将它传递 a void**。任何指针类型都可以隐式转换为void*,但仅在顶层有效:char*可以转换为void*,并且char**可以隐式转换为void*(因为char**是“指针char*”),但char** 不能转换为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;

假设它*values实际上指向 a char***,那么这个转换是有效的,并且在任何代码路径中都没有任何未定义的行为。

于 2013-10-03T22:41:43.863 回答
5

Avoid *只是一个指向未指定类型的指针;它可以是指向 anint或 achar或 achar *或 achar **或任何您想要的任何东西的指针,只要您确保在取消引用时将其视为适当的类型(或可以安全地解释原始类型的类型)作为)。

因此, avoid **只是指向 a 的指针void *,它可以是指向您想要的任何类型的指针,例如 a char *。所以是的,如果您正在分配某些类型对象的数组,并且在一种情况下这些对象是char *,那么您可以使用 avoid **来引用它们,从而为您提供可以称为 a 的东西char ***

直接看到这种构造通常是不常见的,因为通常你将一些类型或长度信息附加到数组,而不是让char ***你有一个struct typed_object **foo或类似的东西,其中struct typed_object有一个类型标记和指针,然后你转换你提取的指针从这些元素到适当的类型,或者你有一个struct typed_array *foo包含类型和数组的结构。

关于风格的几点说明。一方面,做这种事情会使你的代码难以阅读。要非常小心地构建它并清楚地记录它,以便人们(包括你自己)可以弄清楚发生了什么。另外,不要转换 ; 的结果mallocvoid *自动提升为其分配的类型,如果您忘记包含或更新类型声明但忘记更新转换,则转换结果可能会malloc导致细微的错误。<stdlib.h>有关更多信息,请参阅此问题

将声明附加到变量名而不是类型名通常是一个好习惯*,因为这就是它实际解析的方式。下面声明了 onechar和 one char *,但如果你按照你一直写的方式编写它,你可能希望它声明 two char *

char *foo, bar;

或者换个方式写:

char* foo, bar;
于 2013-10-03T22:46:59.167 回答
3

您根本不需要(并且可能不应该)使用 a void **- 只需使用常规void *. 根据 C11 6.3.2.3.1,“指向的指针void可以转换为指向任何对象类型的指针或从指向任何对象类型的指针转​​换。指向任何对象类型的指针可以转换为指向void并再次返回的指针;结果应与原始指针相比较指针。” 一个指针变量,包括一个指向另一个指针的指针,是一个对象。void **不是“指向”的指针void。您可以自由安全地转换为 和 from void *,但不能保证您能够安全地转换为 和 from 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是 type int **,它将被隐式转换为void *by foo()foo()并将其转换回 typeint **并取消引用它以间接修改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_arrayis typechar ***再次被隐式转换为void *foo()并将其转换回并间接new_string_array指向新分配的内存块。

于 2013-10-03T22:44:00.313 回答
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)))))
*/

因此,这是一个基本示例,您可以将其与格式字符串和可变数量的参数一起使用

#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 datatype | 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);
}
于 2013-10-04T00:01:04.233 回答
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

在调用者中,只需将返回值转换为适当的指针,并使用它。

于 2013-10-03T22:54:41.687 回答