Writing a generic function in C, how to handle strings

jit*_*hsk 13 c

I have a function that takes a void** argument and an integer that indicates its datatype

void foo (void** values, int datatype)
Run Code Online (Sandbox Code Playgroud)

Inside the function, depending on the datatype, I malloc it this way:

if (datatype == 1)
    *values = (int*) malloc (5 * sizeof(int));
else if (datatype == 2)
    *values = (float*) malloc (5 * sizeof(float));
Run Code Online (Sandbox Code Playgroud)

All is good upto now. However, when character strings come into the picture, things get complicated. The void** would need to be void***, since I will need to do something like this:

*values = (char**) malloc (5 * sizeof(char*));
for(i=0;i<5;i++)
    (*values)[i] = (char*) malloc (10);
..
strncpy( (*values)[0], "hello", 5);
Run Code Online (Sandbox Code Playgroud)

How should such a situation be handled? Can I pass a char*** to the function that expects a void** but cast it correctly inside it?

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);
}
Run Code Online (Sandbox Code Playgroud)

所以我只是投入了void**一个char***.我尝试了这个并忽略了警告,它运行正常.但这样安全吗?有更优雅的选择吗?

Ada*_*eld 7

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

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

如果你的函数需要a void**,那你最好传递一个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;
Run Code Online (Sandbox Code Playgroud)

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


Bri*_*ell 5

A void *只是指向未指定类型的指针; 它可以是指向inta char,a ,或a char *,或a char **或任何你想要的东西的指针,只要你确保在取消引用时,你将它视为适当的类型(或原始类型可以安全地解释的类型)如).

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

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

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

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

char *foo, bar;
Run Code Online (Sandbox Code Playgroud)

或者写另一种方式:

char* foo, bar;
Run Code Online (Sandbox Code Playgroud)


Cro*_*man 3

您根本不需要(并且可能不应该)使用 a 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  */
}
Run Code Online (Sandbox Code Playgroud)

等等,然后将其称为类似于:

int * new_int_array;
foo(&new_int_array, 1);
Run Code Online (Sandbox Code Playgroud)

&new_int_array类型为int **,它将隐式转换为void *by 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  */
}
Run Code Online (Sandbox Code Playgroud)

依此类推,并称其为:

char ** new_string_array;
foo(&new_string_array, 3);
Run Code Online (Sandbox Code Playgroud)

类似地,&new_string_arrayis 类型char ***再次被隐式转换为void *,并将foo()其转换回来并间接new_string_array指向新分配的内存块。