2

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

/* 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 改为 cmncnt。抱歉,这是拼写错误。

谢谢, 马修 Liju

4

10 回答 10

5

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

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

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

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

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

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

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 *通用寻址机制。
  • 下一个操作添加正确的字节数以到达isize 对象数组的第 th 个元素的开头elemsize
  • 然后第二个演员告诉编译器“相信我 - 我知道我在做什么”和“将此地址视为 CMNCNT 结构的地址”。

从那里开始,代码就足够简单了。请注意,由于 CMNCNT 结构中包含long值,因此我曾经%ldfprintf().

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

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

于 2008-11-15T16:01:04.813 回答
2

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

将某些内容分配给 void 指针的那一刻,原始指向类型的任何类型信息都会丢失,因此只有先将其转换回原始指针类型,才能取消引用。

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

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

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

于 2008-11-14T13:36:43.777 回答
1

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

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

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

于 2008-11-14T05:54:52.603 回答
1

你不能这样做:

cmncnt->_cnt[0]

如果 cmnct 是一个空指针。

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

于 2008-11-14T05:58:00.023 回答
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)

于 2008-11-15T15:48:08.957 回答
0

在这条线上:

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

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

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

于 2008-11-14T05:52:55.120 回答
0

你的表情

(CMNCNT *)&cmncntin[ii*cmncnt_elmsize]

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

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

于 2008-11-14T08:32:34.657 回答
0

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

考虑 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 相对于结构开头的地址可能会改变

例如:

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

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

于 2008-11-15T18:17:39.187 回答
-1

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

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

于 2008-11-14T05:52:21.360 回答
-1

这条线有点折磨,你不觉得吗?

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),并且实际上可能因调用而异,我建议重新考虑这种设计。我想这可能有一个很好的理由,但对我来说它看起来真的很不稳定。我几乎可以保证有更好的设计方法。

于 2008-11-14T14:49:19.763 回答