2017-08-08 54 views
0

我有兩個結構和const char數組:如何在C中動態訪問struct成員?

typedef struct { 
    int skip_lines; 
    int num; // count of files 
    int i; // number of the file to define order; extremly important to set correctly and then not to change! 
    char filename[70]; 
    char main_directory[16]; 
    char submain_directory[100]; 
} FILE_; 

typedef struct { 
    FILE_ radiation_insolation[7]; 
    FILE_ radiation_radiation[5]; 
    FILE_ winds[9]; 
    FILE_ pressure[1]; 
    FILE_ humidity[1]; 
    FILE_ temperature[4]; 
} FILES; 

char *tables[] = {"radiation_insolation", "radiation_radiation", "winds", "pressure", "humidity", "temperature" }; 

我也有在主功能FILES files;併發起從文件加載數據的功能。所以這些文件的每個成員都包含數據。

然後我需要訪問這樣的數據:

files->radiation_insolation[0].skip_lines 
files->radiation_radiation[0].skip_lines 
files->radiation_winds[0].skip_lines 
files->pressure[0].skip_lines 
files->humidity[0].skip_lines 
files->temperature[0].skip_lines 

我的計劃是創建循環來動態處理每一個成員。

for(i = 0; i<6; i++) { 
     // do some job 
    } 

我的問題是如何做到這一點的時候,我需要訪問例如files-> radiation_insolation使用循環中的表[i]?如何創建成員的名稱,以便編譯器知道要訪問的成員?

在PHP語言可以使用類似$文件 - > $表[1]。但如何在C中做到這一點?

+5

你不能。它被稱爲反射,C不支持它。編譯時會丟失變量名稱。 –

+1

你不能。 C沒有[內省](https://en.wikipedia.org/wiki/Type_introspection)或[反思](https://en.wikipedia.org/wiki/Reflection_(computer_programming)),這是需要的這工作。 –

+2

在一個不相關的說明中,如果你在你的程序中使用文件,創建類型名爲FILE_和FILES會導致問題,因爲你有標準的C FILE和你的FILE_和FILES。 。這將是很難閱讀和理解,以及容易犯錯誤。 –

回答

-3

有,你可以在這種情況下,採用一種奇怪的伎倆,但我敢肯定,它依賴於一些假設可能不是真的無處不在。首先,代碼:

struct foo { 
    int a; 
    int b; 
    int c; 
}; 

觀察到,所有的成員都具有相同的類型和,來這裏的假設,可能會奠定了一個又一個在內存中。我們可以用聘用指針數組語義做我們想要的東西:

struct foo myfoo = { 100, 200, 300 }; 
int *p = &myfoo.a; 

int sum = 0; 
sum += p[0]; /* = *(p+0) = address of a + 0 int = a */ 
sum += p[1]; /* = *(p+1) = address of a + 1 int = b */ 
sum += p[2]; /* = *(p+2) = address of a + 2 int = c */ 

對於其中字段相同的尺寸和裝在內存中的情況下,這隻能。

在你的情況,當你跟蹤有多少元素的每個字段有這個只會工作。

+0

我寧願使用補償表。 –

+0

您對原始數據類型的示例值得懷疑,因爲即使它們屬於相同類型,確實也沒有對齊保證。然而,在OP的情況下,每個成員都是一個結構體,因此保證它本身是對齊的,所以當每個成員都是相同類型的結構體時使用這個方法應該沒問題。 – Lundin

+1

@Lundin它仍然是指針算術的東西,這不是一個數組... –

-1

製作的FILES和類似結構union與所有27個元件中的一個FILE_數組:

typedef union 
{ 
    FILES f; 
    FILE_ all[27]; 
} 
FILES_U; 

然後可以訪問files->f.radiation_radiation[0]或相同files->all[7]tables數組將在all(0,7,12 ...)中包含基本索引,而不是字符串名稱。

+0

我沒有看過它,但我會認爲調用UB。 – JeremyP

+1

@JeremyP我很肯定,這是完全正常的,只要沒有其他成員,但只有FILE_'。由於所有項目都是自己對齊/填充的結構,所以對齊不應該成爲問題。並且沒有嚴格的走樣違規。 – Lundin

+1

C標準是否指定相同類型的結構成員之間的填充必須與該類型的數組元素之間的填充相同?如果不是這樣,這個解決方案在技術上是錯誤的,儘管我同意它幾乎肯定會在任何實際的實現中起作用。 – JeremyP

0

答案是你不能。好了,關於一個真實的C編譯器,你可能能夠別名第二結構作爲FILE_一個數組,但我敢肯定它會調用未定義的行爲。我認爲標準中沒有任何內容表示所有成員是相同類型的結構中的填充必須與數組中所有成員都是相同類型的填充相同。

如果它能夠訪問所有成員在聲明中單對你很重要,它可能會更好使用一個實際的數組,並定義一些常量:

enum { 
    radiation_isolation = 0, 
    radiation_radiation = 7, 
    winds = 12, 
    // etc 
} 

FILE_ files[total_files]; 

FILE_ *isolation_3 = &files[radiation_isolation + 3]; 

你可能會寫一些函數使它看起來更好,並提供一些邊界檢查。

+0

因爲每個結構本身都會與填充對齊,所以應該可以安全地將它與數組進行別名。您可以添加額外的檢查以確保是這種情況,但我不確定在實踐中是否有必要。 – Lundin

+0

「應該安全」夠好嗎? – JeremyP

+0

至少我不能在理論和實踐中都想出任何會導致失敗的場景。相反,它隱含地保證以C標準工作。 – Lundin

0

有不是一個真正的方式下的Structs要做到這一點並不表的事,但更接近硬件,即內存塊。

您可以創建一個噁心的宏來訪問結構:

// bad idea 
#define FILES_ITEM(var, field, index, member) var.field[index].member 

但這樣的宏都只是毫無意義的和不好的做法,這是很清晰的鍵入了一切:

int main (void) 
{ 
    FILES files; 

    for(size_t i=0; i<7; i++) 
    { 
    files.radiation_insolation[i].skip_lines = i; 
    printf("%d ", files.radiation_insolation[i].skip_lines); 
    } 
} 

它將通常很難證明除上述風格以外的其他任何事情。


隨着C11,你可以利用共用;組合匿名結構陣列改善情況有點:

#define FILE_ITEMS_N (7 + 5 + 9 + 1 + 1 + 4) 

typedef union { 

    struct 
    { 
    FILE_ radiation_insolation[7]; 
    FILE_ radiation_radiation[5]; 
    FILE_ winds[9]; 
    FILE_ pressure[1]; 
    FILE_ humidity[1]; 
    FILE_ temperature[4]; 
    }; 

    FILE_ items [FILE_ITEMS_N]; 

} FILES; 

然後,您可以個別地訪問成員:

files.radiation_insolation[0].skip_lines = 123; 

或作爲陣列:

files.items[item].skip_lines = 123; 

工會保證通過C11§6.7.2.1工作:

14結構或聯合對象的每個非位字段部件在適當的一個實現定義方式排列 其類型。

/-/

17有可能是在未命名結構或聯合的末端填充。

這意味着內部結構的所有成員都會保證適當地對齊,如果需要,在結尾處填充結尾。

此外,陣列也保證別名,沒有任何問題的個別成員,每C11 6.5/7:

一個目的應具有其存儲的值僅由具有一個左值表達式訪問 以下幾種類型:

/-/

- 聚合或聯合類型包括其 成員(包括,遞歸地在前述類型之一,一個子聚集的成員或包含單on)

+0

'int i'指的是成員數,而不是數組元素:'files.radiation_insolation [i] .skip_lines'是錯誤的。如果我將代碼更改爲:僞代碼:'files.table_type [i] [n] .skip_lines' ...所以'n'維度可以更改。 i維是一個常數。 – user1141649

+0

這裏不應該有一些明確的包裝編譯指示嗎?或者這是C11中定義的行爲? – Groo

+0

@ user1141649然後使用union版本。 – Lundin

-1

一種方法是通過(ab)使用x宏。他們允許你減少重複,代價是你的同事可能發生的憤怒。好處是您只需要在一個地方更新項目列表,並且預處理器將自動生成結構和所有必要的元數據。

I.e.你定義一個簡單的像這樣的條目列表,其中FILE_ENTRY是沒有明確的規定:

#define X_FILE_LIST(X_FILE_ENTRY) \ 
    X_FILE_ENTRY(radiation_insolation, 7) \ 
    X_FILE_ENTRY(radiation_radiation, 5) \ 
    X_FILE_ENTRY(winds, 9) \ 
    X_FILE_ENTRY(pressure, 1) \ 
    X_FILE_ENTRY(humidity, 1) \ 
    X_FILE_ENTRY(temperature, 4) 

,然後定義FILE_ENTRY(name, len)如你所願:

// number of entries 
#define X_EXPAND_AS_COUNT(name, len) 1 + 
const int FILES_count = X_FILE_LIST(X_EXPAND_AS_COUNT) 0; 

// struct definition 
#define X_EXPAND_AS_FIELD(name, len) FILE_ name[len]; 
typedef struct { 
    X_FILE_LIST(X_EXPAND_AS_FIELD) 
} 
FILES; 

// byte offsets of each field 
#define X_EXPAND_AS_BYTEOFFSET(name, len) offsetof(FILES, name), 
int FILES_byte_offsets[] = { 
    X_FILE_LIST(X_EXPAND_AS_BYTEOFFSET) 
}; 

// FILE_ offsets of each field 
#define X_EXPAND_AS_FILEOFFSET(name, len) offsetof(FILES, name)/sizeof(FILE_), 
int FILES_offsets[] = { 
    X_FILE_LIST(X_EXPAND_AS_FILEOFFSET) 
}; 

// sizes of each array 
#define X_EXPAND_AS_LEN(name, len) len, 
int FILES_sizes[] = { 
    X_FILE_LIST(X_EXPAND_AS_LEN) 
}; 

// names of each field 
#define X_EXPAND_AS_NAME(name, len) #name, 
const char * FILES_names[] = { 
    X_FILE_LIST(X_EXPAND_AS_NAME) 
}; 

這將擴大到是這樣的:

const int FILES_count = 1 + 1 + 1 + 1 + 1 + 1 + 0; 

typedef struct { 
    FILE_ radiation_insolation[7]; 
    FILE_ radiation_radiation[5]; 
    FILE_ winds[9]; 
    FILE_ pressure[1]; 
    FILE_ humidity[1]; 
    FILE_ temperature[4]; 
} 
FILES; 

int FILES_byte_offsets[] = { 
    ((size_t)&(((FILES*)0)->radiation_insolation)), 
    ((size_t)&(((FILES*)0)->radiation_radiation)), 
    ((size_t)&(((FILES*)0)->winds)), 
    ((size_t)&(((FILES*)0)->pressure)), 
    ((size_t)&(((FILES*)0)->humidity)), 
    ((size_t)&(((FILES*)0)->temperature)), 
}; 

int FILES_offsets[] = { 
    ((size_t)&(((FILES*)0)->radiation_insolation))/sizeof(FILE_), 
    ((size_t)&(((FILES*)0)->radiation_radiation))/sizeof(FILE_), 
    ((size_t)&(((FILES*)0)->winds))/sizeof(FILE_), 
    ((size_t)&(((FILES*)0)->pressure))/sizeof(FILE_), 
    ((size_t)&(((FILES*)0)->humidity))/sizeof(FILE_), 
    ((size_t)&(((FILES*)0)->temperature))/sizeof(FILE_), 
}; 

int FILES_sizes[] = { 7, 5, 9, 1, 1, 4, }; 

const char * FILES_names[] = { 
    "radiation_insolation", "radiation_radiation", 
    "winds", "pressure", "humidity", "temperature", 
}; 

然後,您可以使用類似的方法重複它:

for (int i = 0; i < FILES_count; i++) 
{ 
    FILE_ * first_entry = (FILE_ *)&files + FILES_offsets[i]; 
    for (int j = 0; j < FILES_sizes[i]; j++) 
    { 
     FILE_ * file = first_entry + j; 
     printf("%s[%d].skip_lines = %d \n", 
      FILES_names[i], 
      j, 
      file->skip_lines); 
    } 
} 

這項工作將通過FILES所有成員進行迭代,並通過每個字段的所有陣列成員:

// output of the program above 
radiation_insolation[0].skip_lines = 0 
radiation_insolation[1].skip_lines = 0 
radiation_insolation[2].skip_lines = 0 
radiation_insolation[3].skip_lines = 0 
radiation_insolation[4].skip_lines = 0 
radiation_insolation[5].skip_lines = 0 
radiation_insolation[6].skip_lines = 0 
radiation_radiation[0].skip_lines = 0 
radiation_radiation[1].skip_lines = 0 
radiation_radiation[2].skip_lines = 0 
radiation_radiation[3].skip_lines = 0 
radiation_radiation[4].skip_lines = 0 
winds[0].skip_lines = 0 
winds[1].skip_lines = 0 
winds[2].skip_lines = 0 
winds[3].skip_lines = 0 
winds[4].skip_lines = 0 
winds[5].skip_lines = 0 
winds[6].skip_lines = 0 
winds[7].skip_lines = 0 
winds[8].skip_lines = 0 
pressure[0].skip_lines = 0 
humidity[0].skip_lines = 0 
temperature[0].skip_lines = 0 
temperature[1].skip_lines = 0 
temperature[2].skip_lines = 0 
temperature[3].skip_lines = 0 

這給你帶來實際的「反思」,它可以讓你找到它的名稱的成員:

FILE_ * get_entry_by_name_and_index(FILES * files, const char * name, int idx) 
{ 
    // NOTE: no bounds checking/safe string function, etc 

    for (int i = 0; i < FILES_count; i++) 
    { 
     if (strcmp(FILES_names[i], name) == 0) 
     { 
      int base_offset = FILES_offsets[i]; 
      return (FILE_ *)files + base_offset + idx; 
     } 
    } 

    return NULL; 
} 

例如,這將讓指針files.winds[4]

FILE_ * item = get_entry_by_name_and_index(&files, "winds", 4); 
assert((void*)item == (void*)&files.winds[4]); 
+0

請注意,如果您在維護過程中對代碼重複有嚴格的要求,則此解決方案是合理的。如果是這樣,那麼這是一個很好的解決方案。如果沒有,那麼這是混淆。 – Lundin

+0

是的,這是與x宏的交易。但不幸的是,他們最接近你在C中的反思。我會說在更新代碼時,混淆仍然會減少由於開發人員錯誤導致的* runtime *錯誤數量。的確,單元測試是爲了抓住這些錯誤,但是仍然沒有比機器生成的代碼更加安全和正確的類型。 – Groo

+0

不必要的太複雜。 – i486