【问题标题】:Can a header use a macro definition defined in a source file?标头可以使用源文件中定义的宏定义吗?
【发布时间】:2020-03-05 15:40:14
【问题描述】:

我正在尝试使用固定长度数组来实现堆栈。我想创建多个 Stack 实例,所以我的 stack 实现在我的 stack.h 标头中。

stack.h


#ifndef STACK_H
#define STACK_H

typedef int Item;

typedef struct stack{
        Item array[SIZE];
        int current_size;
}Stack;

Stack *create_stack(void);
void push_stack(Stack *stack, Item value);
Item pop_stack(Stack *stack);

#endif

但是,我想保持数组的大小私有,所以我在 stack.c 中定义了它

stack.c

#include "stack.h"

#define SIZE 100

Stack *create_stack(void)
{
    ....
}

void push_stack(Stack *stack, Item value)
{
    ....
}

Item pop_stack(Stack *stack)
{
    ....
}

这可能吗?是否在源文件中定义了宏但在头文件中使用? 谢谢

【问题讨论】:

  • 嗯,也许是一个灵活的数组成员? typedef struct stack{ int current_size; Item array[]; }Stack;,但为什么不直接使用 typedef struct stack{ int current_size; Item *array; }Stack; 呢? Jules,你为什么要在Stack 中创建一个未知公共大小的array?答案可以导致更好的方法。
  • 作业规定栈必须使用定长数组。
  • Jules,你想要的是opaque pointer,永远不要向公众展示typedef struct stack{ Item array[SIZE]; int current_size; }Stack;。让所有对Stack 的访问都通过Stack* 和函数进行。

标签: c arrays header stack include


【解决方案1】:

从技术上讲,您应该将 #include 预处理器指令视为“复制和粘贴”指令。

这基本上意味着最终结果是一个包含指定顺序的所有文本的单个文件。

当然,在这种情况下,您的代码中有错误,因为SIZE 仅在在被使用后才定义。

考虑一下,stack.c:

#define SIZE 100
#include "stack.h"

这将允许在stack.h 中使用SIZE。

此外,如果不在标头中定义 SIZE(至少作为后备值),如果不先手动定义 SIZE,您将无法在任何地方包含标头。

考虑,在标题中添加:

#ifndef SIZE
#define SIZE 10
#endif

恕我直言,这种方法不是使用此功能的理想方式,但这应该可以回答您的问题。

恕我直言,最好的方法是使用不完整的类型或“灵活数组”。

例如,您可以使用以下方式定义类型:

typedef struct stack{
        int current_size;
        Item array[];
} Stack;

然后使用(例如)分配足够的内存来管理堆栈大小:

Stack * s = malloc(sizeof(*s) + (SIZE*sizeof(Item)));

但是,我可能会选择将类型设为不透明指针。

这意味着只能使用函数访问数据。

使用不透明指针将提供更强大的划分。

它还可以让您在不破坏 ABI/向后兼容性的情况下更新数据类型。

即,在标题中将自己限制为:

typedef struct stack Struct;

函数将使用指向不完整类型的指针。

Stack *create_stack(void);

这将保证使用标头的任何人都无法访问数据(他们将收到有关访问不完整类型的错误)。

C 文件将只为内部使用定义结构:

struct stack{
        Item array[SIZE];
        int current_size;
};

或者,使用动态尺寸方法,如上所述:

struct stack{
        int current_size;
        Item array[];
};


Stack *create_stack(size_t size)
{
    Stack * s = malloc(sizeof(*s) + (size * sizeof(*s->array)));
    // ....
    return s;
}

附: - 恕我直言

作为关于命名的说明......您似乎使用的是 CamelCase 命名约定。

在 C 语言中,您会发现大多数开发人员倾向于使用 snake_case 而不是 CamelCase 来命名事物。

此外,typedef 通常有一个后缀,类似于 POSIX 和一些标准类型使用的(保留的)_t 后缀(即结构类型可能以 _s 结尾)。

这只是我的个人意见,但作为一个长期阅读代码的人,我发现阅读与一种语言提供的标准库风格相匹配的代码更容易。显然,不是每个人都同意我的观点。

使用这些命名约定,我将这些类型命名为:

typedef int item_i;

typedef struct stack{
        int current_size;
        item_i array[SIZE];
} stack_s;

【讨论】:

  • 使用 CamelCase、下划线、各种后缀是特定机构或团体的惯例或个人喜好。它们不是“C 约定”、“Java 约定”或“C++ 约定”。
  • @EricPostpischil - 约定正是社区最常选择的。没有人(在大多数语言中)会阻止您使用您想要的任何风格;)......但是,这些风格与特定的语言相关联并且它们确实有历史。 snake_case 与 C 相关联,正如标准库、POSIX 和 Linux 内核的风格所见。如果我提到 Pascal 语言风格(如在 PascalCase 中)我可能还没有被理解,但 Java 和 C++ 也与 PascalCase 相关联......嗯,对不起,CamelCase。
  • 与特定语言的关联很弱。它们不是“C 约定”、“Java 约定”或“C++ 约定”。用命名风格的偏见来污染学生的思想是一种浪费和分心。
  • 无论如何,一个好的答案应该关注定义结构和完整与不完整对象类型的语义,而不是关于源文件包含的机制。这是一个关键概念——编译器在编译一个翻译单元时知道和需要知道什么。它需要知道结构中数组的大小才能对结构进行操作(复制/分配它、创建实例等)。与此相比,头文件和宏定义是次要角色。
  • @EricPostpischil - 我更新了答案以澄清这只是我的观点,并删除了按语言明确命名的约定。在其他方面,我们可能只是不同意。
【解决方案2】:

是的,这是很有可能的。您只需将#define SIZE 100 行放在#include "stack.h" 行上方。 C预处理器基本上会将头文件的内容复制/粘贴到包含它的位置,因此如果您在上面定义SIZE包含它,那么头文件的内容将放在它下面编译就好了。

旁注:根据您的设置,您可能能够使用 cpp source.c 仅运行 C 预处理器,并且您将能够看到代码在发送到编译器之前是如何组合在一起的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-11-07
    • 1970-01-01
    • 1970-01-01
    • 2012-08-30
    • 1970-01-01
    • 2015-07-07
    • 1970-01-01
    • 2011-12-19
    相关资源
    最近更新 更多