在不包含头文件的情况下使用C结构体

5

我的基本问题是,我想使用某些在头文件中定义的结构体和函数,但不想在我的代码中包含该头文件。

该头文件是由一个工具生成的。由于我无法访问该头文件,因此无法将其包含在我的程序中。

以下是我的情况的简单示例:

first.h

#ifndef FIRST_H_GUARD
#define FIRST_H_GUARD
typedef struct ComplexS {
   float real;
   float imag;
} Complex;

Complex add(Complex a, Complex b);

// Other structs and functions
#endif

first.c

#include "first.h"

Complex add(Complex a, Complex b) {
   Complex res;
   res.real = a.real + b.real;
   res.imag = a.imag + b.imag;
   return res;
}

my_program.c

// I cannot/do not want to include the first.h header file here
// but I want to use the structs and functions from the first.h
#include <stdio.h>

int main() {
   Complex a; a.real = 3; a.imag = 4;
   Complex b; b.real = 6; b.imag = 2;

   Complex c = add(a, b);
   printf("Result (%4.2f, %4.2f)\n", c.real, c.imag);

   return 0;
}

我的意图是为my_program构建一个目标文件,然后使用链接器将目标文件链接成可执行文件。在C语言中,我想实现的这个目标是否可能?


2
有一些方法可以做到这一点,因为C数据结构实际上是平坦的内存块,但它们不是标准的,而且强烈不建议使用(例如,在结构体内保存偏移量并使用指针算术运算(不推荐))。 - user132014
假设我创建这些虚构的结构体,那么我的add()调用会导致在first.c中定义的add()函数的调用吗? - shams
不过,我也有一个解决办法。我将尝试在我即将发布的糟糕编程示例中添加对此的支持。 - user132014
7个回答

7
为了在my_program.c中使用结构体,必须在my_program.c定义该结构体。这是无法绕过的。
为了定义它,您必须包括first.h或以其他方式在my_program.c中提供Complex的定义(例如将Complex的定义复制并粘贴到my_program.c中)。
如果您的first.h与您发布的内容相同,则当然没有任何复制和粘贴的必要。只需包含您的first.h即可。
如果您不想因为头文件中的其他内容(这里没有显示)而包含first.h,则可以将Complex的定义移动到一个单独的小头文件中,并在两个位置都包含它。

1
问题在于我想使用的结构体包含其他结构体作为同一头文件中(以及可能在其他地方)定义的字段,这个链条会继续很长时间。我的主要目标是能够在这些结构体上调用函数(也在同一头文件中定义)。 - shams
5
@shams:你需要那些结构体和函数的定义。否则编译器就不知道该怎么做了。对于函数,编译器可能会猜测,但通常猜测得很差,会导致代码出错。 - Yann Ramin
3
如果你的函数使用结构体指针作为参数,你可以将它们分离到一个单独的头文件中,并在其中进行结构体的前向声明(例如:typedef struct ComplexS Complex;)。这样做可以使你在主函数中无法查看结构体的内容,但至少你可以调用这些函数。 - user3458

2

我修改了文件,使用指针和前向引用,并使其正常工作。

现在我要检查生成的头文件,看看是否需要使用任何不接受指针作为参数的函数。

这是我最终尝试的代码:

first.h

#ifndef FIRST_H_GUARD
#define FIRST_H_GUARD
typedef struct ComplexS {
   float real;
   float imag;
} Complex;

Complex* new_complex(float a, float b);
Complex* add(Complex* a, Complex* b);
void print_complex(Complex* a);
#endif

first.c

#include <stdio.h>
#include <stdlib.h>
#include "first.h"

Complex* new_complex(float a, float b) {
   Complex* temp = (Complex*)malloc(sizeof(Complex));
   temp->real = a;
   temp->imag = b;
   return temp;
}

Complex* add(Complex* a, Complex* b) {
   Complex *res = new_complex(a->real + b->real, a->imag + b->imag);
   return res;
}

void print_complex(Complex* a) {
   printf("Complex(%4.2f, %4.2f)\n", a->real, a->imag);
}

second.c

#include <stdio.h>

struct ComplexS; // forward declaration
typedef struct ComplexS Complex; 

Complex* new_complex(float a, float b); 
Complex* add(Complex* a, Complex* b); 
void print_complex(Complex* a);

int main() {
   Complex* a = new_complex(3, 4);
   Complex* b = new_complex(6, 2);

   Complex* c = add(a, b);
   print_complex(c);

   return 0;
}

输出:

Complex(9.00, 6.00)

1

您可以在first.c中使用函数包装对结构体成员的所有访问,前向声明结构体(struct ComplexS)在first.c和my_program.c(或公共头文件)中,并且只通过指针在my_program.c中访问结构体(first.c中的所有函数都将操作结构体指针)。

然后,您的程序只需要知道前向声明,而不是结构体成员。

完成后,my_program.c可能会读取:

struct ComplexS;
typedef struct ComplexS Complex;

int main() {
   Complex *a = new_complex(3,4);
   Complex *b = new_complex(6,2);
   Complex *c = add_complex(a, b);
   printf("Result (%4.2f, %4.2f)\n", get_real(c), get_imag(c));

   return 0;
}

1

如果你知道数据是如何存储的(因为你已经向我们展示了它),那么你可以在你的文件中复制它们!如果你不想使用 #include,可以复制粘贴!如果这是通过对数据进行逆向工程得到的结果或其他什么原因,你需要用你的猜测创建一个 struct,以便让编译器正确地访问数据。

另一方面,如果你完全不知道数据是如何存储在结构中的,那么编译器也无法为你知道它。


0

如果你想使用这个结构体,你必须在某个地方定义它。否则,编译器没有足够的信息来构建程序(它甚至不知道一个Complex对象需要多少内存)。仅凭你对结构体的使用,编译器无法确定数据类型的样子。

然而,如果你知道结构体的大小,你仍然可以使用它,尽管是有限制和潜在危险的方式。例如,你可以在C文件中包含以下定义:

typedef char[2*sizeof(float)] Complex;

这将允许您以基本方式(基本上将其视为原始数据块)使用 Complex 数据类型。您 无法 正常访问结构成员,但可以在函数之间传递 Complex 对象(或 Complex* 指针),将它们读写到文件中,并将它们相互比较。如果您勇敢一点,可以使用 char 指针并引用单个字节来访问结构内部的数据。请小心进行此操作,因为它需要深入了解特定编译器如何将结构布局在内存中,包括任何对齐/填充字节。以下是一个通过字节偏移量访问内部成员的示例,假设该结构是“紧凑”的(没有填充):

typedef char[2*sizeof(float)] Complex;

Complex add(Complex a, Complex b) {
  Complex res;
  float real, imag;
  real = ((float*)&a)[0] + ((float*)&b)[0];
  imag = ((float*)&a)[1] + ((float*)&b)[1];
  ((float*)&res)[0] = real;
  ((float*)&res)[1] = imag;
  return res;
}

只要你的Complex定义不改变,这段代码应该会给你与你发布的代码相同的结果。但这样做非常危险,如果你决定这样做,请不要提到我的名字。

这种技术只有在编译时知道“真正”的Complex结构体的确切大小时才有效,并且只允许粗略地访问结构体。任何试图使用点符号(如first.c中的函数)的代码都会抛出编译器错误。要使用点符号访问结构体的内部,需要完整的结构体定义。

如果first.c将被编译成您的代码将链接的库,则可以进行以下修改,以允许您使用add函数而无需使用first.h:

/* In start.c */
Complex add(Complex* a, Complex* b) {
  Complex res;
  res.real = a->real + b->real;
  res.imag = a->imag + b->imag;
  return res;
}

/* In your code */
typedef char[2*sizeof(float)] Complex;
Complex add(Complex* a, Complex* b);

现在,您应该能够创建类型为Complex的对象,并将它们来回传递给add函数,而无需了解结构的内部细节(除了大小)。

我不想重新实现add,我希望能够调用first.c中现有的实现。很可能可以通过改变add()的签名为使用指针来实现。 - shams
@shams- 你想使用 first.c 中的实现吗?它被编译成库了吗?如果是这样,你可以将 add 改为使用指针并且没问题,但是如果你计划将 first.c 编译到你的程序中,它将无法工作(因为 first.c 包括 first.h 并且 add 使用点符号访问 Complex 的成员)。 - bta

0

免责声明:我在现实生活中从不会以这种方式编写代码。

好的,这就是我谈论的那个黑客技巧。只要不在任何重要的代码中使用它,因为它不能保证始终有效,但你会看到内存组织的思路。我鼓励用gcc -S将它们编译成汇编,并查看一下。

root@brian:~# cat struct-hack.c
#include <stdio.h>

struct hack {
        int a, b;
};
int main()
{
        struct hack myhack = { 0xDEAD, 0xBEEF };
        FILE *out = fopen("./out", "w");
        fwrite(&myhack, sizeof(struct hack), 1, out);
        fclose(out);
        return 0;
}
root@brian:~# cat struct-read.c
#include <stdio.h>
#include <stdlib.h>

// in bytes.
#define STRUCT_ADDRESS_MODE     int
#define STRUCT_SIZE     8


int main()
{
        /** THIS IS BAD CODE. DO NOT USE IN ANY REMOTELY SERIOUS PROGRAM. **/

        // Open file
        FILE *in = fopen("./out", "r");
        if(!in) exit(1);

        // We need a way of addressing the structure, an Int is convenient because we
        // know the structure contains a couple ints.
        STRUCT_ADDRESS_MODE *hacked_struct = malloc(STRUCT_SIZE);
        if(!hacked_struct) exit(1);

        fread(hacked_struct, STRUCT_SIZE, 1, in);

        printf ("%x, %x\n", hacked_struct[0], hacked_struct[1]);
        free(hacked_struct);
        fclose(in);
        return 0;
}

root@brian:~# ./struct-hack
root@brian:~# hexdump  -C out
00000000  ad de 00 00 ef be 00 00                           |........|
00000008
root@brian:~# ./struct-read
dead, beef

我想补充的是,这个程序完全滥用了C语言,只有在你真的很自负的时候才使用它。 - user132014
非常感谢你的建议Tom,我可能最终不会使用它。但是这确实帮助我理解了结构体如何在内存中转换。 - shams

0
在 first.h/first.c 中,基于 Complex* 声明和定义 add 函数。同时,在 my_program.c 中基于 Complex* 声明 initialize 和 print 函数。这里的想法是,如果您不想显式地包含头文件或类型声明,则不要直接在代码中使用 Complex 作为类型。
因此,这是您的新主函数:
#include <stdio.h>

struct Complex; // forward declaration
Complex* initialize(int, int); 
Complex* add(Complex*, Complex*); 
void print_complex(Complex*);

int main() {
   Complex* a = initialize(3, 4); // create new complex no 3 + 4i
   Complex* b = initialize(6, 2);

   Complex* c = add(a, b); // redefined add
   print_complex(c);

   return 0;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接