【问题标题】:Array of POINTERS to Multiple Types, C指向多种类型的指针数组,C
【发布时间】:2011-12-09 13:09:21
【问题描述】:

使用malloc是否可以拥有多种类型的数组?

编辑:

目前我有:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define int(x) *((int *) x)


int main() {
        void *a[10];

        a[0] = malloc(sizeof(int));
        int(a[0]) = 4;

        char *b = "yola.";

        a[1] = malloc(strlen(b)*sizeof(char));
        a[1] = b;

        printf("%d\n", int(a[0]));
        printf("%s\n", a[1]);
}

但它很乱。其他方式?

编辑:稍微清理一下。

【问题讨论】:

  • 很多事情都是可能的,但很多都不是一个好主意。使用structs 和unions(后者最好用enum 标记,以便您知道要访问哪个字段)。
  • 除了是个坏主意,还能怎么做?
  • “用枚举标记”是什么意思?
  • 有一个tagged union 的概念,其中一个外部值(标签)告诉您当前正在使用联合的哪个部分。如果您既没有这种方法也没有其他可靠的方法来判断正在使用哪个部分,那么您将面临未定义的行为(或者当您写入字段 A 但读取字段 B 时可能会发生任何标准所说的情况)。跨度>

标签: c arrays pointers void


【解决方案1】:

你不能有一个不同类型的数组,确切地说。但是您可以通过多种不同的方式实现类似的效果(至少在某些目的上)。

如果你只是想把几个不同类型的值打包在一起,但值的数量和类型不变,你只需要一个struct,并且可以通过名字访问它们:

struct s_item {
  int     number;
  char    str[100];
} item;
item.number = 5;
strcpy(item.str,"String less than 100 chars");

如果你知道你可能使用什么类型,你可以创建一个联合,或者一个包含联合的结构,这样你就可以用类型标记它。然后,您可以创建一个数组。 type 成员可让您稍后查看您存储在每个数组元素中的内容。

enum ElementType { et_str, et_int, et_dbl };
struct Element {
  ElementType type;
  union {
    char      *str;
    int       i;
    double    d;
  }
};

struct Element *arr = malloc(sizeof(struct Element) * 3);
arr[0].type = et_str;
arr[0].str = strdup("String value"); /* remember to free arr[0].str */
arr[1].type = et_int;
arr[1].i = 5;
arr[2].type = et_dbl;
arr[2].d = 27.3;

/* access the values.. */
for (int i = 0; i < 3; i++) {
  switch(arr[i].type) {
    case et_str: printf("String: %s\n",arr[i].str); break;
    case et_int: printf("Integer: %d\n",arr[i].i); break;
    case et_dbl: printf("Double: %f\n",arr[i].d); break;
  }
}

/* The strings are dynamically allocated, so free the strings */
for (int i = 0; i < 3; i++)
  if (arr[0].type == et_str) free(arr[0].str);
/* free the malloc'ed array */
free(arr);
/* etc., etc. */

这种方法可能会浪费空间,因为:

  • 每个元素都有一个额外的值来跟踪它所保存的数据类型
  • 结构可能在其成员之间有额外的填充
  • 联合中的类型可能有不同的大小,在这种情况下联合将与最大的类型一样大

如果你有另一种方法知道你在每个元素中存储了什么类型,你可以只使用裸联合而不用结构包装它。这更紧凑一些,但每个元素仍将至少与联合中最大的类型一样大。


您还可以创建一个包含void * 值的数组。如果这样做,则必须以某种方式分配项目并将它们的地址分配给数组元素。然后,您需要将它们转换为适当的指针类型以访问这些项目。 C 不提供任何运行时类型信息,因此无法从指针本身找出每个元素指向的数据类型——您必须自己跟踪。当您存储的类型很大并且它们的大小变化很大时,这种方法比其他方法更紧凑,因为每个都与数组分开分配并且只能提供该类型所需的空间。对于简单类型,使用联合并没有真正获得任何好处。

void **arr = malloc(3 * sizeof(void *));
arr[0] = strdup("Some string"); /* is a pointer already */
arr[1] = malloc(sizeof(int));
*((int *)(arr[1])) = 5;
arr[2] = malloc(sizeof(double));
*((double *)(arr[2])) = 27.3;

/* access the values.. */
printf( "String: %s\n", (char *)(arr[0]) );
printf( "Integer: %d\n", *((int *)(arr[1])) );
printf( "Double: %f\n", *((double *)(arr[2])) );

/* ALL values were dynamically allocated, so we free every one */
for (int i = 0; i < 3; i++)
  free(arr[i]);
/* free the malloc'ed array */
free(arr);

如果您需要跟踪数组中的类型,您还可以使用结构来存储类型和指针,类似于前面使用联合的示例。同样,这仅在存储的类型很大且大小变化很大时才真正有用。

enum ElementType { et_str, et_int, et_dbl };
struct Element {
  ElementType type;
  void        *data;
};

struct Element *arr = malloc(sizeof(struct Element) * 3);
arr[0].type = et_str;
arr[0].data = strdup("String value");
arr[1].type = et_int;
arr[1].data = malloc(sizeof(int));
*((int *)(arr[1].data)) = 5;
arr[2].type = et_dbl;
arr[2].data = malloc(sizeof(double));
*((double *)(arr[2].data)) = 27.3;

/* access the values.. */
for (int i = 0; i < 3; i++) {
  switch(arr[i].type) {
    case et_str: printf( "String: %s\n", (char *)(arr[0].data) ); break;
    case et_int: printf( "Integer: %d\n", *((int *)(arr[1].data)) ); break;
    case et_dbl: printf( "Double: %f\n", *((double *)(arr[2].data)) ); break;
  }
}

/* again, ALL data was dynamically allocated, so free each item's data */
for (int i = 0; i < 3; i++)
  free(arr[i].data);
/* then free the malloc'ed array */
free(arr);

【讨论】:

  • 当我尝试打印它们的值时......奇怪
  • 如果是char *,它只需要强制转换而不需要额外的取消引用。我将编辑以添加打印值的示例。
  • intdouble 有问题。字符串很好!
  • 那么可能缺少对那些的取消引用;无论如何,更新应该显示如何访问它们。
【解决方案2】:

不,所有元素都必须属于同一类型。您可能会摆脱一系列结构。

struct mixed {
    enum {
        INTEGER,
        STRING,
    } type;
    union {
        int num;
        char *str;
    } value;
};


struct mixed v[10];
v[0].type = INTEGER;
v[0].value.num = 10;

我自己永远不会做这样的事情(看起来很乱)。但是你的 array-of-void* 方法是相似的:你必须将类型的信息存储在某个地方。

【讨论】:

  • 如果您可以使用malloc 腾出空间,为什么它们必须是同一类型?
  • 我能有简短的解释吗? :-/
  • 当你说 a[6] 时,编译器会转到地址a + sizeof(*a) * 6。这就是为什么所有元素都需要具有相同大小的原因。
  • @tekknolagi:当您引用数组中的元素时,它会根据元素的大小计算位置。 a[1]a+1*sizeof(void)。不允许使用sizeof(void),因此它无法计算a[1] 或任何其他索引的位置。
  • @tekknolagi 你有点急于接受这个答案。你为什么不等待也许更好的东西出现?
【解决方案3】:

您可以轻松地拥有一个指向不同类型的指针数组。当然,要使其非常有用,您需要有某种方法来记录或确定每个元素当前引用的类型。

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

int main() {

    // This implicitly allocates memory to store 10 pointers
    void *a[10];

    // The first element will be a pointer to an int
    // Allocate the memory it points to, then assign it a value.
    a[0] = malloc(sizeof(int));
    *( (int *)a[0] ) = 4;

    // The second element will be a pointer to char; for simplicity,
    // I'm hardcoding the length of the string + 1 for the null byte.
    a[1] = malloc( 6*sizeof(char) );
    strncpy( a[1], "hello", 5 );

    printf( "%d\n", *( (int *)a[0] ) );
    printf( "%s\n", a[1] );

}

【讨论】:

  • 我明白了...difftypes.c:10: warning: incompatible implicit declaration of built-in function ‘strncpy’
  • 哦,你忘了包含string.h
  • 我使用strncpy 将字符串数据放入malloc 调用分配的内存块中。在您问题中修改后的代码中,您正在分配内存,但随后覆盖了指针值,因此您有内存泄漏。你应该按照我的方式做,或者干脆做a[1] = "..."。我选择为每个元素使用动态分配的内存以保持一致性。在任何情况下,千万不要做类似ptr = malloc(...); 之类的事情,然后给ptr 分配一个新值,而不先free 分配它指向的内存。
  • 好的!可靠的答案:) 但你忘了包括string.h
  • gcc 没有给我你得到的警告。这些东西在编译器之间会有所不同。
【解决方案4】:

我不确定您想要实现什么,但有两种可能性:

1 - 你实际上并不想要一个数组而是一个结构体:

struct {
    int number;
    char *string;
} a;

在这种情况下,您可以使用a.number 访问数字,使用a.string 访问字符串。

2 - 你想要一个变体类型的数组。在 C 中,您可以对变体类型使用联合(最好是标记):

struct Variant {
    int type;
    union {
        int number;
        char *string;
    }
}

然后你可以用 0 代表数字和 1 代表字符串来编码你的类型。当然,使用枚举而不是整数作为类型会更好。

【讨论】:

    【解决方案5】:

    这是因为您试图将值存储到需要指针的插槽中。尝试以下操作(为简洁起见,省略了错误检查)

    int* pIntTemp = malloc(sizeof(int));
    *pIntTemp = 4;
    a[0] = pIntTemp;
    

    【讨论】:

    • 你的意思可能是*pIntTemp = 4
    • @cnicutar 确实如此。接得好。固定
    【解决方案6】:

    最大的问题是让 C 编译器以不同的方式处理数组的每个元素。

    我可以建议一种混合方法吗?

    留出几个指针,每个指针都有相应的结构定义。

    当你决定你想要哪种元素时,使用那个指向 malloc 和设置的指针,然后再使用。

    然后将该指针的值复制到指针数组中。

    稍后,当您想使用该元素时,将数组元素复制到它的适当指针中,以使编译器满意。

    请记住,这只是一个例子,它有一些简短的交流,比如难以排序或在中间插入一个节点,但是......

    例如:

    struct      this_type {
        char        mod_kind[20];
        int         this_int;
    };
    struct      that_type {
        char        mod_kind[20];
        char        that_string[20];
    };
    
    void  *list_o_pointers[10];
    struct  this_type       *p_this;
    struct  that_type       *p_that;
    
    p_this = malloc(sizeof(struct this_type));
    list_o_pointers[0] = p_this;
    strcpy(p_this->mod_kind, "this kind");  // or whatever you want to use to differentate different types
    
    p_that = malloc(sizeof(struct that_type));
    list_o_pointers[0] = p_that;
    strcpy(p_that->mod_kind, "that kind");
    
    // later
    p_this = list_o_pointers[0];
    p_that = list_o_pointers[0];
    if (strstr(p_this->mod_kind, "this kind")) { /* do this stuff */ }
    if (strstr(p_that->mod_kind, "that kind")) { /* do that stuff */}
    

    它解决了诸如必须强制转换 *((double *)(arr[2].data)) = 之类的问题,并且还有助于提高可读性。

    如果您有许多不同的节点结构,这可能会崩溃。

    这有点蛮力,但是(恕我直言)它对大脑来说更容易一些。数组是一个简单的数组,每个节点都很简单。节点不需要像链表那样的“下一个”指针。

    标记。

    【讨论】:

      猜你喜欢
      • 2017-08-10
      • 2012-05-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-25
      相关资源
      最近更新 更多