C 中的结构、内部结构和大小

Structures, internal structures and size in C

我正在用 C 为嵌入式、资源受限的设备编程。

我有这样的结构:

typedef struct UjThread{
    struct{
        UInt32 runInstr;            
        UInt8* mailbox;     
    }appBucket;

    struct{
        UInt32 appId;                   
        UInt32 numInstr;            
        UInt32 allocMem;            
        UInt32 eepromStartAddr; 
    }appContract;

    UInt16 spBase;      //we use an empty ascending stack
    UInt16 spLimit; //also used for "isPtr"
    UInt16 localsBase;
    UInt32 stack[];

}UjThread;

我为每个对象启动一个线程并分配所需的内存(此结构为 92 字节,但我没有显示所有字段)。 但是,有些对象不会使用内部 appContract 和 appBucket 结构,但仍会为这些结构分配内存。

有没有办法避免这种情况?将内部结构指定为可选,或者提取这些内部结构的大小并将其从内存分配中减去?

我可以制作两个单独的结构,每种类型的对象一个,但我不想这样做,因为我必须在任何地方调整我的代码才能使用两种类型的线程。

除了明显的 - 使用两个结构之外,我只看到另外两种可能性。 要么使用指向单独分配的 appContract 的指针,要么如果您需要的某些数据是互斥的,请使用联合。

如果您的可选字段位于 struct 的开头,您可以调整已分配对象的地址,以便可选字段驻留在未分配的内存中。使用 offsetof 宏来确定强制数据从哪里开始:

offsetof(UjThread, spBase) // in bytes

按此数量调整分配大小:

UjThread *newThread;
if (NoOptionalFields())
{
    size_t sizeReduce = offsetof(UjThread, spBase);
    size_t size = sizeof(UjThread) - sizeReduce;
    newThread = (void*)((char*)malloc(size) - sizeReduce);
}
else
{
    newThread = malloc(sizeof(UjThread));
}

要释放内存,别忘了将指针调回来:

if (NoOptionalFields())
{
    size_t sizeReduce = offsetof(UjThread, spBase);
    free((char*)newThread + sizeReduce);
}
else
{
    free(newThread);
}

顺便说一句,因为您的 struct 中有一个 "flexible array member",实际大小的计算比我的示例更复杂。但是你明白了 - 只需从分配大小和结果指针中减去可选字段的大小。

考虑在 C 中工作的单一继承的实现。

定义一个包含两个对象共有的所有元素的基本结构。请注意,我已将 stack 成员的类型更改为指针,因为在该设计中这必须是单独的分配。

typedef struct ThreadBase{
    UInt16 spBase;      //we use an empty ascending stack
    UInt16 spLimit; //also used for "isPtr"
    UInt16 localsBase;
    UInt32 *stack;
}ThreadBase;

然后声明另一个包含基础对象的结构作为第一个成员并附加额外的东西。

typedef struct ThreadExtra{
    ThreadBase base;

    struct{
        UInt32 runInstr;            
        UInt8* mailbox;     
    }appBucket;

    struct{
        UInt32 appId;                   
        UInt32 numInstr;            
        UInt32 allocMem;            
        UInt32 eepromStartAddr; 
    }appContract;
}ThreadExtra;

现在您可以为只需要基本内容的线程定义 ThreadBase 对象。您可以为需要更多的线程定义一个 ThreadExtra 对象。但是您可以将 ThreadExtra 对象强制转换为 ThreadBase,因为 ThreadBase 是 ThreadExtra 的第一个成员。因此,在不处理 ThreadExtra 元素的通用代码中,您可以将所有 Thread 对象视为 ThreadBase 对象。

如果堆栈有固定大小,您可以使用惯用的 C 风格单继承:

typedef struct {
  int a;
} Base;

typedef struct {
  Base base;
  int b;
} Derived;

void useBase(Base *);

void test(void) {
  Base b;
  Derived d;
  useBase(&b);
  useBase(&d.base); // variant 1
  useBase((Base*)&d); // variant 2
}

唉,堆栈没有固定的大小,所以有点惯用的如果不必要地摇摇欲坠的变体 2 将不起作用,但变体 1 会:

typedef struct {
  int a[];
} Small;

typedef struct {
  int b;
  Small small;
} Large;

void useBase(Base *);

void test(void) {
  Small s;
  Large l;
  useBase(&s);
  useBase(&l.small);
}