在C中的struct中“解除引用`void *'指针”警告的解决方案?

时间:2008-11-14 05:45:57

标签: c struct void-pointers

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

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

...

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

我创建了下面的结构来打印上面提到的结构数组。编译下面的代码片段时,我解除了引用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更改为cmncntin。对不起,这是错误的错误。

谢谢, Mathew Liju

10 个答案:

答案 0 :(得分:5)

我认为你的设计会失败,但我也不相信我看到的其他答案完全解决了原因。

看起来你正在尝试使用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 - 它更安全!我一般都在使用“sed 's/^/ /' file.c”为cut'n'paste准备代码到SO答案。)

那个演员阵容做什么?我很高兴你问...

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

从那里,代码很容易。请注意,由于CMNCNT结构包含long值,因此我使用%ld将事实告诉fprintf()

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

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

答案 1 :(得分:2)

虚空的类型故意不完整。由此可见,你不能取消引用void指针,也不能取其大小。这意味着您不能像使用数组一样使用下标运算符。

当您将某个内容指定给void指针时,指向类型的原始类型的任何类型信息都将丢失,因此只有在首次将其强制转换回原始指针类型时才能取消引用。

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

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

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

答案 2 :(得分:1)

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

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

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

答案 3 :(得分:1)

你不能这样做:

cmncnt->_cnt[0]

如果cmnct是一个void指针。

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

答案 4 :(得分:1)

功能

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)

答案 5 :(得分:0)

在这一行:

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

您正在尝试声明一个名为cmncnt的新变量,但具有此名称的变量已作为该函数的参数存在。您可能希望使用其他变量名来解决此问题。

此外,您可能希望将指向CMNCNT的指针传递给函数而不是void指针,因为编译器将为您执行指针运算,而不必转换它。当你用它做的所有内容被转换为CMNCNT时,我没有看到传递空指针的意义。 (顺便说一句,这不是数据类型的描述性名称。)

答案 6 :(得分:0)

你的表达

(CMNCNT *)&cmncntin[ii*cmncnt_elmsize]

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

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

答案 7 :(得分:0)

信息点:内部填充可以搞砸了。

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

某些程序集操作无法正常处理错误对齐的数据。 (例如,如果一个int跨越两个记忆单词。)(是的,我之前已被此咬过。)

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

E.g:

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数组传递给你的函数而不是结构。 E.g:

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而改变。那里有趣的调试时间。带有union'ed _cnt有效负载的单个结构将避免这种情况。)

E.g:

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

第五: 如果你必须使用结构... C ++,iostreams和模板化将会更加清晰。

E.g:

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. */

但那可能不是你想要的......

答案 8 :(得分:-1)

C不是我的杯子o'java,但我认为你的问题是“void * cmncnt”应该是CMNCNT * cmncnt。

现在可以随意纠正我,C程序员,并告诉我这就是为什么java程序员不能拥有好东西。

答案 9 :(得分:-1)

这条线有点折磨,不要想?

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

如何更像

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

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

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

那也应该除去警告,因为你不再解除引用空格*。

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