2017-08-08 51 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