使用main.c中在module.h中声明并在module.c中定义的结构

时间:2018-12-27 15:54:16

标签: c gcc

上下文

我们有三个文件:

  • module.h:它保存结构的声明,
  • module.c:它包含结构的定义,
  • main.c:它包含该结构的一个实例。

目标是通过使用API​​(module.h)在main.c中使用结构,而不是直接通过操作结构成员。这就是为什么结构的定义在module.c中而不在module.h中的原因。

代码

module.h

#ifndef MODULE_H
#define MODULE_H

typedef struct test_struct test_struct;

void initialize_test_struct(int a, int b, test_struct * test_struct_handler);

#endif

module.c

#include "module.h"

struct test_struct
{
    int a;
    int b;
};

void initialize_test_struct(int a, int b, test_struct * test_struct_handler)
{
    test_struct_handler->a = a;
    test_struct_handler->b = b;
}

main.c

#include "module.h"

int main(void)
{
    test_struct my_struct;  // <- GCC error here
    test_struct * my_struct_handler = &my_struct;

    initialize_test_struct(1, 2, my_struct_handler);

    return 0;
}

问题

如果使用GCC编译这些文件,则会出现以下错误:

  

main.c:7:17:错误:“ my_struct”的存储大小未知

问题

我们如何强制使用API​​,从而禁止直接使用结构的成员来操纵结构,而结构的声明和定义与main.c不在同一个模块中?

4 个答案:

答案 0 :(得分:3)

由于MAXERROR函数看不到Copy的定义,因此不能创建该对象的实例,也不能访问其成员。但是,您可以为其创建一个 pointer 。因此,在module.c中需要一个为实例分配内存并返回指向它的指针的函数。您还需要使用函数来读取成员。

在module.h中:

test_struct

在module.c中:

main

在main.c中:

test_struct *allocate_test_struct();
int get_a(test_struct *p);
int get_b(test_struct *p);

答案 1 :(得分:0)

  

如果使用GCC编译这些文件,则会出现以下错误:

     
    

main.c:7:17:错误:“ my_struct”的存储大小未知

  

首先将结构放在头文件中的原因是为了使用户源(在这里是 main.c )可以访问其成员...

您的编译器不知道无定义结构typedef struct test_struct test_struct;的地址,因此&my_struct不会给您地址!

并且没有地址,您无法获得任何变量的大小


说明:

test_struct my_struct;

在这里,您创建了一个不完整类型的变量,因此无效并且没有地址,因为其成员不可访问...

test_struct * my_struct_handler = &my_struct;

然后在这里传递 my_struct 的地址,该地址显然无法获得(该结构在标头内为 definition-less ,并且源代码经过编译,因此可以也无法访问)...

因此,在这种情况下,请使用指针,以便可以将临时地址分配给不完整类型

/* Don't forget to change to
 *   'void initialize_test_struct(int a, int b, test_struct ** test_struct_handler)'
 *   in the header file!
 */
void initialize_test_struct(int a, int b, test_struct ** test_struct_handler)
{
    // Allocate an undefined address to the pointer...
    *test_struct_handler = malloc(sizeof(test_struct));
    (*test_struct_handler)->a = a;
    (*test_struct_handler)->b = b;
}

// The declarations have to be present inside the headers as well...
// A function that returns the pointer to the variables a and b respectively...
// These functions can readily change their values while returning them...
int * get_a_ref(test_struct * test_struct_handler)
{
    return &test_struct_handler->a;
}

int * get_b_ref(test_struct * test_struct_handler)
{
    return &test_struct_handler->b;
}

并像这样在main中使用它:

#include <stdio.h>

#include "module.h"

int main(void)
{
    test_struct * my_struct_handler;

    // Here a address is malloc'd to the pointer and the value is assigned to it...
    initialize_test_struct(1, 2, &my_struct_handler);

    // Will change the value of 'a' and similar for b as well...
    // *get_a_ref(my_struct_handler) = 10; 

    printf("%d\n", *get_a_ref(my_struct_handler));
    printf("%d\n", *get_b_ref(my_struct_handler));

    return 0;
}

只是提醒您typedef s的魔术(和晦涩)...

答案 2 :(得分:0)

您不能仅使用包含文件直接实例化一个test_struct,因为在编译器中处理C文件时,其细节未知。该语言仅允许您将 pointers 初始化为大小未知的对象,而不是对象本身。 test_struct的大小和其他详细信息只有在处理模块时才由编译器知道。c

要解决此问题,您需要让module.c分配数据并在初始化调用中提供指向它的指针。这意味着您必须让initialize函数返回一个指向新创建的对象的指针(该对象是malloc或全局或静态对象),或者使该函数接受test_struct **:

void initialize_test_struct(int a, int b, test_struct ** test_struct_handler)
{
    *test_struct_handler = malloc(sizeof(test_struct));
    //Do rest of init.  You should also check return value of malloc
}

//Alternatively
test_struct * initialize_test_struct(int a, int b)
{
    test_struct *temp;
    temp = malloc(sizeof(test_struct);
    //Init members as needed
    return temp;
}

通常在这种情况下,typedef用于指向不透明结构的指针,并被命名为表明它本身不是结构本身-'typedef struct test_struct * test_struct_handle'可能是适当的,因为结构名称本身对用户来说是无用的模块的地址(用于创建指针除外)。

这也是一个好习惯:

  • 具有访问器功能(如果需要)(对主文件执行此操作-请参见dbush的答案)
  • 具有“ de-init” /“ free”功能。用户不一定知道使用了malloc,拥有一个de-init函数可以隐藏更多的实现细节。

答案 3 :(得分:0)

首先,您认为您的库的客户端想要做什么,而您的API不支持,他们会被诱使操纵您结构的字段来完成?考虑扩展API。

一种选择是强制客户端代码从工厂函数中获取其结构,而不是自己分配它们。

另一种方法是创建一个假定义,可能仅包含char数组以建立最小大小和对齐方式,以便客户端代码知道足以分配它们的数组以及库模块本身可以投射指针并旋转位。

另一种方法是将定义放在标头中,并添加一条注释,说明这些字段是可能会更改的内部实现细节。

另一种是移植到面向对象的语言。