[英]Passing an array of 'typedef struct' to a function
我有以下情況:
文件Ac:
typedef struct element
{
uint16_t value_raw;
float value_scaled;
char *desc;
} element;
element sv[REG_READ_COUNT];
文件啊:
typedef struct element element;
文件Bc:
#include "A.h"
void dostuff (element sv[]) { }
在編譯時我得到“錯誤:數組類型具有不完整的元素類型”為Bc中的函數參數定義
這樣做的正確方法是什么? 如何將'element'類型的數組傳遞給函數?
在Bc
, element
是一個不完整的類型(它沒有在Ah
定義,只在Ac
定義)。 C不允許具有不完整元素類型的數組聲明符(如您所發現的)。 以下是C99草案的相關文字:
6.7.5.2數組聲明符
約束
- 除了可選的類型限定符和關鍵字
static
,[
和]
可以分隔表達式或*
。 如果它們分隔表達式(指定數組的大小),則表達式應具有整數類型。 如果表達式是常量表達式,則其值應大於零。 元素類型不應是不完整或函數類型 。 可選的類型限定符和關鍵字static
只出現在具有數組類型的函數參數的聲明中,然后僅出現在最外層的數組類型派生中。
強調我的。 這適用於所有數組聲明符,無論它們出現在何處:在變量聲明,typedef,函數參數列表等中。
要修復代碼,請將完整的結構定義放在Ah
。 或者,如果dostuff
實際上不需要使用元素(例如,只是將“數組”傳遞給其他函數),則可以使用void dostuff(element *sv)
。
重現錯誤的最小代碼。
struct element;
void dostuff (struct element sv[]) { }
使用coliru測試clang
和gcc
: http ://coliru.stacked-crooked.com/a/e5e314deef461290
結果:GCC和clang總是抱怨類型數組不完整類型的參數,而不是指向不完整類型的指針。
相關標准報價:
6.7.6.3函數聲明符(包括原型)
[...]
4調整后,作為該函數定義一部分的函數聲明符中參數類型列表中的參數不應具有不完整類型。
[...]
7參數聲明為''類型數組''應調整為''限定指向類型'',其中類型限定符(如果有)是在數組類型派生的[
和]
中指定的那些。 如果關鍵字static
也出現在數組類型派生的[
和]
中,則對於每次對函數的調用,相應實際參數的值應提供對數組的第一個元素的訪問,其中至少有指定的元素數量。按大小表達式。
好吧,到這里看起來像不完整類型的數組對於參數類型來說完全沒問題,即使在定義中也是如此。
6.2.5類型
[...]
20可以從對象和函數類型構造任意數量的派生類型,如下所示:
- 數組類型描述了具有特定成員對象類型的連續分配的非空對象集,稱為元素類型。 只要指定了數組類型,元素類型就應該是完整的。 數組類型的特征在於它們的元素類型和數組中的元素數。 數組類型據說是從其元素類型派生的,如果它的元素類型是T,則數組類型有時稱為''T'數組。 從元素類型構造數組類型稱為“數組類型派生”。
對於每種情況,上面的引用明確禁止使用具有不完整類型的數組語法。
結論:所有這些編譯器似乎都是正確的,即使這種限制似乎是不必要的。
無論如何,正確的過程不是對類型進行前向聲明,而是將類型本身的聲明放入頭文件中,除非它是一個不透明的類型。
在這種情況下,您必須直接使用參數類型的指針語法。
您的編譯錯誤由Deduplicator的答案描述。
您可以通過編寫element *sv
來解決此問題。 但是, Bc
只能看到定義typedef struct element element;
。 它無法看出構成element
。
如果“真實”版本dostuff
做任何事情與sv
需要知道什么實際結構包含,那么你需要定義移動struct element
從Ac
到Ah
。
作為提供一種方法來執行OP所需但是假設他需要數據隱藏的第二個答案,我提出了基於我的第一個答案構建的代碼,並提供對一個C
文件中的元素類型的泛型訪問,並僅提供不透明的數據類型在頭文件中。 請注意,為了明確指出什么是指針,我使用了element *
但是它們可以全部被ELEM_HANDLE
替換,我將其定義為標題中的類型。 ELEM_HANDLE
抽象出我們正在處理元素指針的事實。 由於我們使用opaque類型,因此我們提供可以調用的方法(在element.h
定義)以處理我們的opaque類型。
element.h展開:
#include <stdint.h>
typedef struct element element;
typedef element *ELEM_HANDLE;
extern element *element_new();
extern void element_delete(element *elem);
extern void element_set_value_raw(element *elem, uint16_t value_raw);
extern uint16_t element_get_value_raw(element *elem);
extern void element_set_value_scaled(element *elem, float value_scaled);
extern float element_get_value_scaled(element *elem);
extern void element_set_desc(element *elem, char *desc);
extern char *element_get_desc(element *elem);
element.c:
#include <stdint.h>
#include <stdlib.h>
typedef struct element
{
uint16_t value_raw;
float value_scaled;
char *desc;
} element;
element *element_new()
{
return calloc(1, sizeof(element));
}
void element_delete(element *elem)
{
free(elem);
}
void element_set_value_raw(element *elem, uint16_t value_raw)
{
elem->value_raw = value_raw;
}
uint16_t element_get_value_raw(element *elem)
{
return elem->value_raw;
}
void element_set_value_scaled(element *elem, float value_scaled)
{
elem->value_scaled = value_scaled;
}
float element_get_value_scaled(element *elem)
{
return elem->value_scaled;
}
void element_set_desc(element *elem, char *desc)
{
elem->desc = desc;
}
char *element_get_desc(element *elem)
{
return elem->desc;
}
testelem.c:
#include <stdio.h>
#include "element.h"
#define REG_READ_COUNT 2
void dostuff(element *sv[], int arrLen)
{
int index;
element *curelem;
uint16_t raw;
float scaled;
char *desc;
for (index = 0; index < arrLen ; index++){
curelem = sv[index];
raw = element_get_value_raw(curelem);
scaled = element_get_value_scaled(curelem);
desc = element_get_desc(curelem);
/* Do more interesting stuff here */
printf("%s, %d, %.4f\n", desc, raw, scaled);
}
}
int main()
{
unsigned int index;
element *sv[REG_READ_COUNT]; /* array of element pointers*/
char desc1[] = "The answer to everything";
char desc2[] = "OtherStuff";
/* Initialize an array of pointers to element items */
for (index = 0; index < sizeof(sv) / sizeof(element *); index++)
sv[index] = element_new();
element_set_value_raw(sv[0], 42);
element_set_value_scaled(sv[0], 6.66f);
element_set_desc(sv[0], desc1);
element_set_value_raw(sv[1], 123);
element_set_value_scaled(sv[1], 456.7f);
element_set_desc(sv[1], desc2);
dostuff(sv, REG_READ_COUNT);
/* free the array of pointers to element items*/
for (index = 0; index < sizeof(sv) / sizeof(element *); index++)
element_delete(sv[index]);
return 0;
}
請注意,我采用自由將數組長度傳遞給元素指針數組旁邊的dostuff
。 這為dostuff提供了足夠的信息來確定數組中有多少元素。 這應該在C89或更高版本和C ++編譯器上正確編譯(並運行)(假設您將.c
文件重命名為.cpp
)。
我提出這個答案,因為使用前向聲明和不透明類型是創建了多少“C”語言共享對象。 此機制允許將元素源編譯為獨立的庫或共享對象,並在不知道element
數據類型的外觀的情況下使用。 本質上,我們在使用我們和庫的模塊之間提供接口契約。 如果我們修改element.cpp
結構元素的內部結構,那么使用它的模塊將不需要重新編譯(只需重新鏈接)。 如果我們修改接口(合同),則需要重建使用該庫的客戶端代碼。
因此,最后,前向引用(opaque類型)可用於隱藏C
數據類型內部並提供一個抽象層。 共享對象( .so
文件)經常使用這種類型的機制來構建可由C
程序使用的復雜庫。
因為Ah只定義了一個opaque類型的typedef struct element element
Bc不可能知道元素的組成甚至確定它的大小。 所以它無法創建這些結構的數組。 如果您希望此代碼工作,則必須將Ac中的整個typedef移動到Ah。 如果你這樣做,那么沒有信息隱藏,並且通過標題可以獲得完整的結構。
另外,您可以創建一個指向結構的指針數組(即使它可能不完整)並將其傳遞給您的函數,但您將無法直接訪問任何結構成員變量。
在指向這些類型的指針數組中使用不透明數據類型的示例:
typedef struct element element;
#define REG_READ_COUNT 100
void dostuff (element *sv[])
{
sv++; /* get next pointer to element */
};
int main()
{
element *sv[REG_READ_COUNT]; /* array of pointers to element */
dostuff(sv);
}
這個代碼很好,直到它需要任何需要實際類型大小的東西。 我們甚至無法將數據成員初始化為任何內容,而無需額外的粘合代碼(另一個模塊)實際上可以訪問完整的元素類型。
您可以擁有指針數組(甚至是不完整類型)的原因是因為指針是C中的基本類型。它既不是不完整類型也不是函數類型。 指針具有固定大小,編譯器可以使用它來生成指針數組。
6.7.5.2數組聲明符
約束
除了可選的類型限定符和關鍵字static之外,[和]可以分隔表達式或*。 如果它們分隔表達式(指定數組的大小),則表達式應具有整數類型。 如果表達式是常量表達式,則其值應大於零。 元素類型不應是不完整或函數類型 。 可選的類型限定符和關鍵字static只出現在具有數組類型的函數參數的聲明中,然后僅出現在最外層的數組類型派生中。
因為指針不是不完整的類型或函數類型,所以即使它們指向不完整的類型,也可以創建它們的數組。 指向不完整類型的指針不會使指針不完整。 你無法取消引用它,並希望直接對它做任何有用的事情。 我直接說,因為在數據隱藏技術和不透明指針中你可以提供間接機制來處理不透明指針數據。
下面是一個代碼示例,該代碼無法以與OP類似的方式進行編譯。 我們認為可以傳遞指向不完整類型的指針(函數參數),但它們仍然不能用作函數內的數組:
typedef struct element element;
#define REG_READ_COUNT 100
void dostuff (element *sv) /* Completely legal but useless if you intend to use it as an array */
{
sv++; /* This should fail - as we are doing array arithmetic on
* an incomplete type. Can't find the starting point of the next
* array element without knowing the size of the object */
};
int main()
{
element sv[REG_READ_COUNT]; /* array of elements will also fail - size of object unknown */
dostuff(sv);
}
這幾乎與前一個相同。 在這一個中,我們有一個指向不完整類型的指針sv
作為函數參數(這來自nneonneo答案)。 這完全合法,因為它只是一個指針。 然而,嘗試對其進行數組運算(在body函數中使用++
)將失敗,因為它需要知道元素的大小並且它是未知的。 ++和 - 或索引數組是未定義的行為(大多數符合標准的編譯器都會拋出錯誤)。 ISO / IEC 9899:TC2說:
6.3.2其他操作數
6.3.2.1左值,數組和函數指示符
...
2除非它是sizeof運算符的操作數,一元&運算符,++運算符, - 運算符或者左運算符。 運算符或賦值運算符,沒有數組類型的左值將轉換為存儲在指定對象中的值(並且不再是左值)。 如果左值具有限定類型,則該值具有左值類型的非限定版本; 否則,該值具有左值的類型。 如果左值具有不完整類型且沒有數組類型,則行為未定義
有關不透明類型的更多信息,請點擊此處
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.