对 C 字符串数组使用简单的 void 指针

Posted

技术标签:

【中文标题】对 C 字符串数组使用简单的 void 指针【英文标题】:using simple void pointer for arrays of C strings 【发布时间】:2022-01-19 23:45:35 【问题描述】:

所以我有这段代码,目标是有一个void *data 指针,它有时用于存储一个简单的int,有时是一个char 数组,有时我需要存储一个char 数组的数组。 我确保我始终知道我存储在 void 指针中的数据类型。

代码在在线解析器中执行良好,这是它的输出:

sizeof 2 x char*: 8

str0: string1 addr: 2995278544

str1: bla2 addr: 2995278576

checking strings:

str0: string1 addr: 2995278544

str1: bla2 addr: 2995278576

计划是为 n 个 char* 指针分配空间并将该指针保存为 void *data。然后将类型更改为“char **ptr”(指向指针的指针),这样我就可以将 strdup 返回的地址保存到该数组并稍后访问它们。 checkItems(uint8_t) 正是这样做的,它通过再次将其更改为“char **ptr”来重新访问“void *data”指针,以便能够访问保存实际 C 字符串的内存地址。

这一切都正确吗?有人会这样做吗?我应该对 void *data 指针使用某种类型的转换,而不是简单地说“char **ptr = data;”吗?

谢谢!

#include<stdio.h>
#include<stdint.h>
#include<string.h>
#include<stdlib.h>
#include<stdarg.h>

void copyItems(uint8_t num, ...);
void checkItems(uint8_t num);

void *data;

int main()

    copyItems(2, "string1", "bla2");
    checkItems(2);


void copyItems(uint8_t num, ...)

    printf("sizeof %u x char*: %u\r\n", num, sizeof(char*), sizeof(char*)*num);
    data = malloc(sizeof(char*)*num);
    
    char **ptr = data;
    
    va_list ap;
    va_start(ap, num);
    
    for (uint8_t n = 0; n < num; n++)
    
        ptr[n] = strdup(va_arg(ap, char*));
        printf("str%u: %s addr: %u\r\n", n, ptr[n], ptr[n]);
    
    
    va_end(ap);


void checkItems(uint8_t num)

    char **ptr = data;
    
    printf("checking strings:\r\n");
    
    for (uint8_t n = 0; n < num; n++)
    
        printf("str%u: %s addr: %u\r\n", n, ptr[n], ptr[n]);
    

【问题讨论】:

“我确保我始终知道我存储在 void 指针中的数据类型。” 在哪里?我本来希望该信息与指针一起包含在struct 中,存储的项目数。 是的,实际上 void 指针包含在存储所有信息(当然包括项目数)的结构中。因为我对此没有任何问题,所以我想让示例尽可能简单。 您不应该(需要)强制转换 void 指针,请参阅Do I cast the result of malloc? 我想我也可以说 ((char**)data)[n] 而不是创建 char **ptr。问题仍然存在,无论这是允许的,正确的和良好的做法,还是纯粹的 BS... 如果您要取消引用 void* 指针,那么您必须强制转换它。对于分配给另一个指针类型,没有。 【参考方案1】:

我会在结构中存储大小和类型信息

typedef enum

    INT, 
    CHAR,
    CHARPTR,
DATA_TYPE;

typedef struct 

    DATA_TYPE dtype;
    size_t size;
    void *data;
DATA;

DATA *copyItems(size_t num, DATA_TYPE type, ...);
void PrintItems(const DATA *data);

size_t datasize(const DATA *data)

    size_t result;
    switch(data -> dtype)
    
        case INT:
            result = sizeof(int);
            break;
        case CHAR:
            result = 1;
            break;
        case CHARPTR:
            result = sizeof(char *);
            break;
        default: 
            result = 0;
            break;
    
    return result;





int main()

    DATA *data =  copyItems(2, CHARPTR, "string1", "bla2");
    PrintItems(data);


DATA *copyItems(size_t num, DATA_TYPE type, ...)

    DATA *data = malloc(sizeof(*data));
        
    va_list ap;
    va_start(ap, type);
    
    if(data)
    
        data -> size = 0;
        data -> data = malloc(datasize(data) * num);
        data -> dtype = type;
        if(data -> data)
        
            for (size_t n = 0; n < num; n++, data -> size++)
            
                switch(data -> dtype)
                
                    case INT:
                        ((int *)data -> data)[n] = va_arg(ap, int);
                        break;
                    case CHARPTR:
                        ((char **)data -> data)[n] = strdup(va_arg(ap, char *));
                        break;
                    default:
                        break;
                
            
        
        else
         /* error handler */
    
    va_end(ap);
    return data;


void PrintItems(const DATA *data)

    if(data && data -> size && data -> data)
    
        for(size_t i = 0; i < data -> size; i++)
        
            switch(data -> dtype)
            
                case INT:
                    printf("[%zu] = %d\n", i, ((int *)data -> data)[i]);
                    break;
                case CHARPTR:
                    printf("[%zu] = %s\n", i, ((char **)data -> data)[i]);
                    break;
                default:
                    break;
            
        
    

https://godbolt.org/z/PjWhrGvvP

【讨论】:

谢谢,我实际上已经有一个存储 void 指针的结构,我很可能会像你一样将必要的信息保存在结构中。无论如何,这让我同意这种方法可以并且允许/工作。但是我很好奇您对@arfneto 的联合方法有何看法。哪种方法更好或更常见/最佳实践?谢谢!【参考方案2】:

这是一种变体记录,存在于 C 和许多语言(如 Pascal)中,也许你可以坚持通常的做法。

C 中,我们可以使用匿名联合来实现此效果。

我将向您展示一个简短的示例。

请注意,我不会在这里实现将字符串复制到 新载体,对安全很重要。如果你请评论 认为你需要一个例子。

数据结构示例

typedef struct

    unsigned char id;  // 1,2,3
    union
    
        unsigned char the_char;
        unsigned char size;  // up to 255 strings
    ;
    union
    
        int          the_int;
        const char** argv;
    ;

 Data;

第一个联合的原因如下所述,但它也有助于保持Data 的大小均匀,并且有时用于对齐目的。

第二个联合是可选的,存在于 non-char 的情况下,因此Data 的最小大小为 2 个字节。

这里我们使用id 作为1 用于char2 用于int3 用于字符串向量。

当持有单个 char 时,它会跟随 id,所以我们只使用 2 个字节

当持有 int 时,它遵循这 2 个字节

当使用字符串时,代码有 2 个选项:

对于最多 255 个字符串,计数放在 size 组件中,为 Data 的第二个字节提供另一种含义。 当size0 时,向量假定为以空值结尾,这在有大量字符串会溢出大小时很有用

输出


char: '?'

int: Value is 42

array of strings: [4 in total]

        #1      an
        #2      array
        #3      of
        #4      strings


array of strings [null-terminated]:

        #1      an
        #2      array
        #3      of
        #4      strings
        #5      ends here

5 strings were found...

代码

#include <iso646.h>
#include <stdio.h>

typedef struct

    unsigned char id;  // 1,2,3
    union
    
        unsigned char the_char;
        unsigned char size;  // up to 255 strings
    ;
    union
    
        int          the_int;
        const char** argv;
    ;

 Data;

int check_one(Data*);
int valid_id(Data*);

int main(void)

    const char* words[] = "an",      "array",     "of",
                           "strings", "ends here", NULL;

    Data data[4];

    data[0].id       = 1;
    data[0].the_char = '?';

    data[1].id      = 2;
    data[1].the_int = 42;

    data[2].id   = 3;
    data[2].size = 4;
    data[2].argv = words;

    data[3].id   = 3;
    data[3].size = 0;  // signals the array as null-terminated
    data[3].argv = words;

    for (int i = 0; i < 4; i += 1) check_one(&data[i]);

    return 0;


int check_one(Data* d)

    const char* what[] = NULL, "char", "int", "array of strings";
    if (d == NULL) return -1;
    if (not valid_id(d)) return -2;
    switch (d->id)
    
        case 1:
            printf("\n%s: '%c'\n", what[d->id], d->the_char);
            break;
        case 2:
            printf("\n%s: Value is %d\n", what[d->id], d->the_int);
            break;
        case 3:
            if (d->size != 0)
              // known size
                printf("\n%s: [%d in total]\n\n", what[d->id], d->size);
                for (int i = 0; i < d->size; i += 1)
                    printf("\t#%d\t%s\n", 1 + i, d->argv[i]);
                printf("\n");
                return 0;
            ;
            // ok: the array is NULL-terminated
            printf("\n%s [null-terminated]:\n\n", what[d->id]);
            int sz = 0;
            for (; d->argv[sz] != NULL; sz += 1)
                printf("\t#%d\t%s\n", 1 + sz, d->argv[sz]);
            printf("\n%d strings were found...\n\n", sz);
            break;
        default:
            break;
    
    return 0;

int valid_id(Data* d)  return ((d->id > 0) && (d->id < 4)); ;

【讨论】:

有趣的方法,感谢您的解释! (为什么)你认为这比@0_______ 的方法更好? 我没有说它是好是坏。直到现在我什至没有读过另一个。我能说的是我写的很灵活:(a)很容易改变实现和添加新的消息类型。 (b) 易于序列化,以便作为缓冲区传输或写入文件。 (c) 这两个功能使更改更容易 (d) 在实际实现中您需要准备和复制数据 (e) 数据包括格式,因此您确实需要“确保”任何内容 这是写这个的常用方法,就像在 Pascal variant records

以上是关于对 C 字符串数组使用简单的 void 指针的主要内容,如果未能解决你的问题,请参考以下文章

Java中怎么对数组中的字符串进行排序

指针中容易混淆的概念以及常见笔试题

C语言总结_数组与函数传参练习题

C语言总结_数组与函数传参练习题

二级指针的应用

C语言指向二维数组的指针