在C中编写泛型函数,如何处理字符串

时间:2013-10-03 22:23:18

标签: c

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

5 个答案:

答案 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 *只是指向未指定类型的指针;它可以是指向intcharchar *,或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

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