对 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
用于char
,2
用于int
和3
用于字符串向量。
当持有单个 char
时,它会跟随 id
,所以我们只使用 2 个字节
当持有 int
时,它遵循这 2 个字节
当使用字符串时,代码有 2 个选项:
对于最多 255 个字符串,计数放在size
组件中,为 Data
的第二个字节提供另一种含义。
当size
为0
时,向量假定为以空值结尾,这在有大量字符串会溢出大小时很有用
输出
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 指针的主要内容,如果未能解决你的问题,请参考以下文章