【问题标题】:Solution for "dereferencing `void *' pointer" warning in struct in C?C中结构中“取消引用`void *'指针”警告的解决方案?
【发布时间】:2010-09-22 07:19:46
【问题描述】:

我试图创建一个伪超结构来打印结构数组。我的基本 结构如下。

/* Type 10 Count */
typedef struct _T10CNT
{
    int _cnt[20];
} T10CNT;

...

/* Type 20 Count */
typedef struct _T20CNT
{
    long _cnt[20];
} T20CNT;
...

我创建了以下结构来打印上述结构的数组。编译下面的代码 sn-p 时出现取消引用 void 指针错误。

typedef struct _CMNCNT
{
    long  _cnt[3];
} CMNCNT;

static int printCommonStatistics(void *cmncntin, int cmncnt_nelem, int cmncnt_elmsize)
{
    int ii;
    for(ii=0; ii<cmncnt_nelem; ii++)
    {
        CMNCNT *cmncnt = (CMNCNT *)&cmncntin[ii*cmncnt_elmsize];
        fprintf(stout,"STATISTICS_INP: %d\n",cmncnt->_cnt[0]);
        fprintf(stout,"STATISTICS_OUT: %d\n",cmncnt->_cnt[1]); 
        fprintf(stout,"STATISTICS_ERR: %d\n",cmncnt->_cnt[2]);
    }
    return SUCCESS;
}

T10CNT struct_array[10];
...
printCommonStatistics(struct_array, NELEM(struct_array), sizeof(struct_array[0]);
...

我的意图是有一个通用的函数来打印所有的数组。请告诉我正确的使用方法。

提前感谢帮助。

编辑:参数名称由 cmncnt 更改为 cmnctin。抱歉,是拼写错误。

谢谢, 马修·李菊

【问题讨论】:

  • 你应该只使用联合,它会更简单: typedef union { long cnt_long;诠释cnt_int; } 碳纳米管; CNT 计数[20];
  • 请解释一下,你所说的超级结构是什么意思,并检查你发布的示例代码。你真的是说: typedef struct _T10CNT { int _cnt[20]; }
  • 超级结构是可以指向具有相似数据类型的相似结构的结构。在上述示例中,TL10CNT、TL20CNT 属于类似类型。我的意图是,为了打印结构,我不需要为每个结构编写函数。相反,我可以使用单个结构指针来获取数据。
  • 以 _ 开头并后跟大写字母的符号名称由 C/C++ 标准库的实现保留。你真的应该改变你的命名约定。
  • 也许你应该告诉我们你想做什么?尝试在 C 中打印出任何结构是不可能的。这不是 C++

标签: c struct void-pointers


【解决方案1】:

C is not my cup o'java,但我认为你的问题是“void *cmncnt”应该是 CMNCNT *cmncnt。

现在请随时纠正我,C 程序员,告诉我这就是为什么 java 程序员不能有好的东西。

【讨论】:

  • 它不应该是 CMNCNT * 参数;这将使丑陋出现在调用函数的所有代码中。使用 void * 至少隐藏了函数实现中的可怕之处。 (而且不是我给你投了反对票——我不开车投反对票。)
【解决方案2】:

在这一行:

CMNCNT *cmncnt = (CMNCNT *)&cmncnt[ii*cmncnt_elmsize];

您正试图声明一个名为 cmncnt 的新变量,但同名的变量已作为函数的参数存在。您可能想使用不同的变量名来解决这个问题。

此外,您可能希望将指向 CMNCNT 的指针而不是 void 指针传递给函数,因为编译器将为您执行指针运算,而您不必强制转换它。当您对它所做的所有事情都将其转换为 CMNCNT 时,我看不到传递 void 指针的意义。 (顺便说一下,这对数据类型来说不是一个非常具有描述性的名称。)

【讨论】:

  • 对不起,这是一个错字。参数为cmcncntin。
【解决方案3】:

将函数声明更改为 char *,如下所示:

static int printCommonStatistics(char *cmncnt, int cmncnt_nelem, int cmncnt_elmsize)

void 类型不假定任何特定大小,而 char 将假定一个字节大小。

【讨论】:

  • 当我将 void * 更新为 char * 编译时抛出警告“从不兼容的指针类型传递 `printCommonStatistics' 的 arg 1”
  • 好吧,我猜你在调用函数时必须强制转换,例如。 printCommonStatistics((char )mydata, elements, elementize) 但它现在开始变得混乱。为什么需要将结构传递为 void
  • void * 可以转换为任何数据类型。该函数不需要关心参数是char类型还是int类型或自定义类型。
  • 是的,但是您没有将 void* 转换为任何类型,这就是编译器感到困惑的原因。
  • 虽然使用 'char *' 是解决方案的一部分,但在函数接口中使用 'void *' 是更好的选择。
【解决方案4】:

你不能这样做:

cmncnt->_cnt[0]

如果 cmnct 是一个空指针。

您必须指定类型。您可能需要重新考虑您的实施。

【讨论】:

    【解决方案5】:

    你的表情

    (CMNCNT *)&cmncntin[ii*cmncnt_elmsize]
    

    尝试获取 cmncntin[ii*cmncnt_elmsize] 的地址,然后将该指针转换为类型 (CMNCNT *)。无法获取 cmncntin[ii*cmncnt_elmsize] 的地址,因为 cmncntin 的类型为 void*。

    研究 C 的运算符优先级并在必要时插入括号。

    【讨论】:

    • 你是对的。我错误地向原始发布者解释了为什么编译器给出了“取消引用 void 指针错误”而不是“我的意图是有一个通用函数来打印所有数组。请让我知道正确的使用方法。”你发了代码吗?
    【解决方案6】:

    void 的类型故意不完整。由此可知,您不能取消引用 void 指针,也不能获取它的 sizeof。这意味着您不能像数组一样使用下标运算符。

    当你给一个 void 指针赋值时,原来指向的类型的任何类型信息都会丢失,所以你只能在你先将它强制转换回原来的指针类型时才能取消引用。

    首先也是最重要的,您将T10CNT* 传递给函数,但您尝试在函数中将其类型转换(并取消引用)到CMNCNT*。这是无效且未定义的行为。

    对于每种类型的数组元素,您都需要一个函数 printCommonStatistics。所以,有一个 printCommonStatisticsIntprintCommonStatisticsLongprintCommonStatisticsChar,它们的第一个参数不同(一个取 int*,另一个取 long*,依此类推)。您可以使用宏创建它们,以避免冗余代码。

    传递结构本身不是一个好主意,因为那时您必须为结构中包含的数组的每个不同大小定义一个新函数(因为它们都是不同的类型)。所以最好直接传递包含的数组(struct_array[0]._cnt,为每个索引调用函数)

    【讨论】:

      【解决方案7】:

      这句话有点折磨,你不觉得吗?

      CMNCNT *cmncnt = (CMNCNT *)&cmncntin[ii*cmncnt_elmsize];
      

      来点类似的怎么样

      CMNCNT *cmncnt = ((CMNCNT *)(cmncntin + (ii * cmncnt_elmsize));
      

      或者更好,如果 cmncnt_elmsize = sizeof(CMNCNT)

      CMNCNT *cmncnt = ((CMNCNT *)cmncntin) + ii;
      

      这也应该消除警告,因为您不再取消引用 void *。

      顺便说一句:我不确定您为什么要这样做,但如果 cmncnt_elmsize 有时不是 sizeof(CMNCNT),并且实际上可能因调用而异,我建议重新考虑这种设计。我想这可能有一个很好的理由,但对我来说它看起来真的很不稳定。我几乎可以保证有更好的设计方法。

      【讨论】:

      • CMNCNT cmncnt = ((CMNCNT *)(cmncntin + (ii * cmncnt_elmsize)); 仍然会失败 cmncntin is void 当然:)
      • 不,不会的。你真的测试过吗?
      • 当您尝试使用 void 指针进行指针运算时,Visual C++ 会发出“错误 C2036: 'void *' : unknown size”。如果您使用 -Wpointer-arith 或 -pedantic,GCC 会发出警告。
      【解决方案8】:

      功能

      static int printCommonStatistics(void *cmncntin, int cmncnt_nelem, int cmncnt_elmsize)
      {
          char *cmncntinBytes;
          int ii;
      
          cmncntinBytes = (char *) cmncntin;
          for(ii=0; ii<cmncnt_nelem; ii++)
          {
              CMNCNT *cmncnt = (CMNCNT *)(cmncntinBytes + ii*cmncnt_elmsize);  /* Ptr Line */
              fprintf(stdout,"STATISTICS_INP: %d\n",cmncnt->_cnt[0]);
              fprintf(stdout,"STATISTICS_OUT: %d\n",cmncnt->_cnt[1]); 
              fprintf(stdout,"STATISTICS_ERR: %d\n",cmncnt->_cnt[2]);
          }
          return SUCCESS;
      }
      

      为我工作。

      问题在于,在注释为“Ptr Line”的行上,代码添加了一个指向整数的指针。由于我们的指针是 char * 我们在内存 sizeof(char) * ii * cmncnt_elemsize 中向前移动,这是我们想要的,因为 char 是一个字节。您的代码试图向前执行 sizeof(void) * ii * cmncnt_elemsize 的等效操作,但 void 没有大小,因此编译器给了您错误。

      我会将 T10CNT 和 T20CNT 更改为都使用 int 或 long 而不是每个都使用一个。你依赖于 sizeof(int) == sizeof(long)

      【讨论】:

        【解决方案9】:

        我认为您的设计会失败,但我也不相信我看到的其他答案完全可以解决更深层的原因。

        您似乎正在尝试使用 C 来处理泛型类型,这总是很麻烦。如果你小心点,你可以做到,但这并不容易,在这种情况下,我怀疑它是否值得。

        更深层次的原因:假设我们已经解决了单纯的句法(或仅是句法)问题。您的代码显示 T10CNT 包含 20 个int,而 T20CNT 包含 20 个 long。在现代 64 位机器上 - 除了在 Win64 下 - sizeof(long) != sizeof(int)。因此,打印函数中的代码应该区分取消引用int 数组和long 数组。在 C++ 中,有一条规则是您不应该尝试多态地处理数组,这就是原因。 CMNCNT 类型包含 3 个long 值;尽管数组的基本类型与 T20CNT 匹配,但在数量上与 T10CNT 和 T20CNT 结构不同。

        风格推荐:我强烈建议避免在名称中使用前导下划线。通常,以下划线开头的名称保留给实现使用,并用作宏。宏不尊重范围;如果实现定义了一个宏 _cnt 它会破坏你的代码。保留哪些名称存在细微差别。我不打算讨论这些细微差别。认为“保留以下划线开头的名称”要简单得多,它会让您避免麻烦。

        样式建议:您的打印函数无条件返回成功。这是不明智的;您的函数不应返回任何内容,以便调用者不必测试成功或失败(因为它永远不会失败)。仔细观察函数返回状态的编码人员将始终测试返回状态,并具有错误处理代码。那段代码永远不会被执行,所以它是死的,但任何人(或编译器)都很难确定。

        Surface Fix:我们可以暂时假设您可以将intlong 视为同义词;但是,您必须改掉认为它们是同义词的习惯。 void * 参数是说“此函数采用不确定类型的指针”的正确方式。但是,在函数内部,您需要先将 void * 转换为特定类型,然后再进行索引。

        typedef struct _CMNCNT
        {
            long    count[3];
        } CMNCNT;
        
        static void printCommonStatistics(const void *data, size_t nelem, size_t elemsize)
        {
            int i;
            for (i = 0; i < nelem; i++)
            {
                const CMNCNT *cmncnt = (const CMNCNT *)((const char *)data + (i * elemsize));
                fprintf(stdout,"STATISTICS_INP: %ld\n", cmncnt->count[0]);
                fprintf(stdout,"STATISTICS_OUT: %ld\n", cmncnt->count[1]); 
                fprintf(stdout,"STATISTICS_ERR: %ld\n", cmncnt->count[2]);
            }
        }
        

        (我也喜欢名为stout 的文件流的想法。建议:在真实源代码上使用 cut'n'paste ——这样更安全!我通常使用“@ 987654333@" 准备将代码剪切粘贴到 SO 答案中。)

        那条投射线是做什么的?很高兴你问...

        • 第一个操作是将const void *转换成const char *;这允许您对地址执行字节大小的操作。在标准 C 之前的日子里,char * 代替 void * 作为通用寻址机制。
        • 下一个操作将正确的字节数添加到大小为 elemsize 的对象数组的第 ith 元素的开头。
        • 然后第二个转换告诉编译器“相信我 - 我知道我在做什么”和“将此地址视为 CMNCNT 结构的地址”。

        从那里开始,代码就很简单了。注意,由于CMNCNT结构中包含long的值,所以我用%ldfprintf()说实话。

        由于您不打算修改此函数中的数据,因此像我一样使用const 限定符不是一个坏主意。

        请注意,如果您要忠实于sizeof(long) != sizeof(int),那么您需要两个单独的代码块(我建议单独的函数)来处理“int 数组”和“@987654346 数组” @' 结构类型。

        【讨论】:

          【解决方案10】:

          信息点:内部填充确实会搞砸。

          考虑 struct { char c[6]; }; -- 它的 sizeof()=6。但是如果你有一个数组,每个元素可能会被填充到 8 字节对齐!

          某些汇编操作不能优雅地处理未对齐的数据。 (例如,如果一个 int 跨越两个内存字。)(是的,我以前被这个咬过。)

          .

          第二:过去,我使用可变大小的数组。 (那时我很笨......)如果你不改变类型,它会起作用。 (或者如果你有一个类型的联合。)

          例如:

          struct T { int sizeOfArray;  int data[1]; };
          

          分配为

          T * t = (T *) malloc( sizeof(T) + sizeof(int)*(NUMBER-1) );
                                t->sizeOfArray = NUMBER;
          

          (尽管填充/对齐仍然会让你搞砸。)

          .

          第三:考虑:

             struct T {
               int sizeOfArray;
               enum FOO arrayType;
               union U { short s; int i; long l; float f; double d; } data [1];
              };
          

          它解决了知道如何打印数据的问题。

          .

          第四:您可以将 int/long 数组传递给您的函数而不是结构。例如:

          void printCommonStatistics( int * data, int count )
          {
            for( int i=0;  i<count;  i++ )
              cout << "FOO: " << data[i] << endl;
          }
          

          通过以下方式调用:

          _T10CNT  foo;
          printCommonStatistics( foo._cnt, 20 );
          

          或者:

           int a[10], b[20], c[30];
          printCommonStatistics( a, 10 );
          printCommonStatistics( b, 20 );
          printCommonStatistics( c, 30 );
          

          这比在结构中隐藏数据要好得多。当您将成员添加到您的结构之一时,布局可能会在您的结构之间发生变化并且不再一致。 (意味着 _cnt 相对于结构开头的地址可能会因 _T10CNT 而不是 _T20CNT 发生变化。有趣的调试时间在那里。具有联合的 _cnt 有效负载的单个结构可以避免这种情况。)

          例如:

          struct FOO {
            union {
                   int     bar  [10];
                    long biff [20];
             } u;
          }
          

          .

          第五: 如果你必须使用结构...... C++、iostreams 和模板会更容易实现。

          例如:

          template<class TYPE> void printCommonStatistics( TYPE & mystruct, int count )
          {
            for( int i=0;  i<count;  i++ )
              cout << "FOO: " << mystruct._cnt[i] << endl;
          }      /* Assumes all mystruct's have a "_cnt" member. */
          

          但这可能不是你要找的……

          【讨论】:

          • "考虑 struct { char c[6]; }; -- 它的 sizeof()=6。但是如果你有一个数组,每个元素可能会被填充为 8 字节对齐!” - 不。该标准要求 sizeof 计算相同的填充,因此在这种情况下为 8。
          猜你喜欢
          • 2021-03-21
          • 2014-06-01
          • 2015-01-04
          • 2016-05-12
          • 2012-05-23
          • 1970-01-01
          • 2011-06-17
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多