2

考虑一个 C 结构,它表示单链表中的一个条目。它包含指向一些任意数据的指针、该数据的大小以及查找下一个条目的方法

typedef struct{
  unsigned char *data
  unsigned char dataSize
  unsigned char nextEntry
} Entry;

接下来,考虑以下条目集合及其代表的数据:

unsigned char dataA[3];
unsigned char dataB[16];
unsigned char dataC[17];

Entry entryA = {dataA, sizeof(dataA), 3}; //It's important for "3" to match up with the index of entryB once it's put into MasterList below.
Entry entryB = {dataB, sizeof(dataB), 4}; //Likewise
Entry entryC = {dataC, sizeof(dataC), 0}; //0 terminates the linked list
Entry emptyEntry = {(void*)0, 0, 0};

Entry MasterList[8] = {
entryA,     //Index 0 - Contains dataA and points to Index 3 as the next Entry in a linked list
emptyEntry, //Index 1 - Unused (or used for something else)
emptyEntry, //Index 2 - Unused
entryB,     //Index 3 - Contains dataB and points to Index 5 as the next Entry in a linked list
entryC,     //Index 4 - Contains dataC and terminates the linked list
emptyEntry, //Index 5 - Unused
emptyEntry, //Index 6 - Unused
emptyEntry};//Index 7 - Unused

我的问题:你能想出一种在编译时自动计算“nextEntry”值的方法吗?现在,如果有人打乱 MasterList 中条目的顺序或添加一些其他数据并抵消一些条目,则存在巨大的潜在错误。我们通过单元测试或集成测试捕获了所有错误,但不可避免的是,对 MasterList 的任何更改最终都会被检查两次。一次是有人编辑它,第二次是在代码测试失败时修补链表索引。

我最初的直觉是“不,那太愚蠢了”和“你为什么还要试试这个?” 但我过去曾见过一些令人印象深刻的 C-Macro 魔法。我也相信任何宏观魔法都会比上面的更糟糕,但我认为值得一试,对吧?

澄清- 我坚持使用 MasterList 数组(而不是正确的链表),因为信息的消费者期望它是这种方式。事实上,那里还有其他不属于需要位于固定索引的链表的信息。然后,最重要的是,有这个链表数据。它是一个链表的原因是使它在添加其他具有固定索引的元素时不会被推来推去。例如,如果我们需要在索引 3 处插入一个特定的字符串,entryB 和 entryC 可能会被推开,但仍然可以从链表头部(固定在索引 0)开始发现走清单。

4

4 回答 4

3

通过重置和使用行号:

#line 0 
#define NUM ( __LINE__ -2)
Entry MasterList[8] =
{ {dataA, sizeof(dataA), NUM }
, {(void*)0, 0, NUM }
, {(void*)0, 0, NUM }
, {dataB, sizeof(dataB), NUM }
, {dataC, sizeof(dataC), NUM }
, {(void*)0, 0, NUM }
, {(void*)0, 0, NUM }
, {(void*)0, 0, NUM }
};

gcc -E 的输出:

# 28 "index.c"
#pragma #line 0 __FILE__
# 1 "index.c"

Entry MasterList[8] =
{ {dataA, sizeof(dataA), ( 2 -2) }
, {(void*)0, 0, ( 3 -2) }
, {(void*)0, 0, ( 4 -2) }
, {dataB, sizeof(dataB), ( 5 -2) }
, {dataC, sizeof(dataC), ( 6 -2) }
, {(void*)0, 0, ( 7 -2) }
, {(void*)0, 0, ( 8 -2) }
, {(void*)0, 0, ( 9 -2) }
};

应该可以在不重置行号的情况下做到这一点。

于 2012-08-01T19:18:23.573 回答
0

听起来您应该只使用实际的链表,而不是让每个节点的数据猜测最后一个条目的索引。

您对预处理器的诡计持开放态度,但您通过让运行时访问数据而产生了设计问题,这些数据应该在不应该存在的编译时强制执行。一方面,每个节点都必须拥有它甚至不知道它所在的数组的全局知识。我知道 C 没有 C++/Java/C# 的很多魔力,更不用说 Perl/Python/Ruby 但这是只是一个数据结构问题而不是语言特征问题。

从评论编辑:

struct SpecialArray { 
    Entry* entries[10000]; //you'll have to write your own real memory management
    int lastIdx;
};

void addEntry(struct SpecialArray* arr, Entry* entry) {
    arr->entries[arr->lastIdx] = entry;
    entry->nextEntry = arr->lastIdx+1;
    lastIdx++;
}

听起来您实际上需要评论中的 Entry[] 而不是 *Entry[] ,但这可以通过为 Entry 添加深拷贝功能来实现。然后,(&arr.entries)只要您的客户端代码需要一个 Entry 数组,您就可以通过。该例程addEntry将为您管理索引问题并解决您的原始问题。

于 2012-08-01T19:53:19.157 回答
0

使用编译器或预处理器可用的工具似乎没有真正的好方法。我认为最好的方法是使用代码生成工具来生成已经连接了链表的数组。

于 2012-08-11T21:27:15.443 回答
-1

不要用索引来做,这不值得一点点可能的收益。并且不要使用单独的对象,而是在原地初始化。这是另一个玩具示例,它甚至有next指针const限定,所以没有人可以乱用它:

#include <stddef.h>

typedef struct data data;

struct data {
  char const* D;
  size_t len;
  data*const next;
};

#define STR_INITIALIZER(STR) .D = STR, .len = (sizeof(STR) - 1)
#define DATA_INITIALIZER(NAME, STR, HERE) [HERE] = { STR_INITIALIZER(STR), &(NAME)[HERE+1], }

data table[] = {
  DATA_INITIALIZER(table, "something", 0),
  DATA_INITIALIZER(table, "something else", 1),
  { 0 }
};

这使用了 C99 的“指定初始化程序”功能,但您可能会提供一个可以不用的版本。

如果您真的希望全部由宏完成,那么您可以使用P99进行展开,类似以下内容应该可以使用"p99_for.h"(未​​经测试)

#define TABLE_ENTRIES(NAME, ...) P99_FOR(NAME, P99_NARG(__VA_ARGS__), P00_SEQ, DATA_INITIALIZER, __VA_ARGS__)

data table[] = {
  TABLE_ENTRIES(table, "something", "something else"),
  { 0 }
};
于 2012-08-01T19:33:17.247 回答