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

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

您根本不需要(也可能不应该)使用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 ** ,它将被foo()隐式转换为void *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_arraychar ***类型,再次被隐式转换为void *foo()将其转换回来并间接使new_string_array指向新分配的内存块。

应如何处理这种情况? 我可以将char***传递给期望void**但在其中正确投射的函数吗?

不,这是技术上未定义的行为。 它似乎可以在您的计算机上运行,​​但它可能会在将来实现具有不同表示forms的不同指针类型的计算机上失败,这是C语言标准所允许的。

如果你的函数需要void** ,那么你最好将它传递给void** 。 任何指针类型都可以隐式转换为void* ,但只能在顶层工作: char*可以转换为void*char**可以隐式转换为void* (因为char**是“指向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实际上指向char*** ,那么此强制转换是有效的,并且在任何代码路径中都没有任何未定义的行为。

void *只是指向未指定类型的指针; 它可以是一个指向intcharchar *char **或任何你想要的东西的指针,只要你确定当你取消引用时,你将它视为合适的类型(或者原始类型可以安全地解释为)。

因此, void **只是指向void *的指针,它可以是指向任何类型的指针,例如char * 。 所以,是的,如果你要分配某些类型的对象的数组,并且在一种情况下这些对象是char * ,那么你可以使用void **来引用它们,给你一些可以被称为char ***

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

关于风格的几个笔记。 首先,做这种事情可能会使您的代码难以阅读。 要非常小心地构建它并清楚地记录它,以便人们(包括你自己)可以弄清楚发生了什么。 另外,不要转换malloc的结果; void *自动提升到它所分配的类型,如果你忘记包含或更新类型声明但忘记更新强制转换,则转换malloc的结果会导致细微的错误。 有关详细信息,请参阅此问题 。

将声明中的*附加到变量名称而不是类型名称通常是一个好习惯,就像它实际解析的那样。 下面声明了一个char和一个char * ,但是如果你按照你编写它们的方式编写它,你可能会期望它声明两个char *

 char *foo, bar; 

或者写另一种方式:

 char* foo, bar; 

有一个内置机制可以做到这一点,还有额外的好处,它允许可变数量的参数。 通常以这种格式看到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但我离题了)

这是一种类型的值forms:

 #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); } 

有了一些技巧,你可以做到。 见例子:

 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 

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