【问题标题】:Is it a good practice to hide structure definition in C?在 C 中隐藏结构定义是一种好习惯吗?
【发布时间】:2016-03-21 21:44:32
【问题描述】:

在我看来,在 C 中隐藏结构的定义通常会使代码更安全,因为您在编译器的帮助下强制要求结构的任何成员都不能被直接访问。

但是,它有一个缺点,即结构的用户无法声明其类型的变量以放入堆栈,因为结构的大小以这种方式变得不可用(因此,用户必须求助于通过malloc() 在堆上分配,即使它是不可取的)。

这可以(部分)通过所有主要 libc 实现中存在的 alloca(3) 函数解决,即使它是 does not conform to POSIX

考虑到这些优点和缺点,这样的设计总体上可以认为是好的吗?

lib.h:

struct foo;
extern size_t foo_size;
int foo_get_bar(struct foo *);

lib.c:

struct foo {
  int bar;
};

size_t foo_size = sizeof foo;

int foo_get_bar(struct foo *foo)
{
  return foo->bar;
}

example.c:

#include "lib.h"

int bar(void)
{
  struct foo *foo = alloca(foo_size);
  foo_init(foo);
  return foo_get_bar(foo);
}

【问题讨论】:

  • 更常见的选择是使用foo_createfoo_destroy 之类的东西,这意味着您不会暴露结构的任何细节,并且可以做更高级的操作比如在内部存储malloc'd 指针。您实际上想要使用alloca 的情况很少见,除了malloc 和朋友非常有限的嵌入式系统。
  • 如果结构是不透明的,那么让客户端代码需要分配或声明该类型的任何变量(如示例中所示)将是糟糕的设计。结构的所有实例都应该来自库本身。
  • VLA[] 允许吗? (C99)?声明foo_size 的字符数组(使用alignas)可能有效。然而,总的来说,同意@kaylum
  • @chqrlie,是的,我在发表评论后不久就看到了,然后在您的回复到达这里之前删除了评论。
  • 一旦您获得了符合您目标的候选解决方案,请将其清理、简化并尝试在codereview.stackexchange.com 上发布以获取更多反馈。准备好接受一些强烈的反馈。

标签: c memory-management struct alloca


【解决方案1】:

函数bar 调用未定义的行为:foo 指向的结构未初始化。

如果您要隐藏结构详细信息,请提供分配一个并初始化它的foo_create() 和释放所有资源并释放它的foo_finalize

您提出的建议可能会起作用,但容易出错且不是通用解决方案。

【讨论】:

  • 修复了代码。正如我所说,如果使用foo_create(),那么就不可能在堆栈上分配内存,这有时是可取的。
  • @AlexanderSolovets 在实践中,这通常是不可取的。允许在堆栈上分配和拆除对象并不是很好。这意味着您无法对实例进行任何清理。
  • 如果你想继续使用alloca你可以有一个foo_init函数来初始化它。正如 chux 在 cmets 中提到的那样,您还需要结构的对齐方式,因此用户调用 foo_create 会容易得多,而不必担心任何这些。
  • @AlexanderSolovets,您问“这样的设计通常可以被认为是好的吗?”答案是不”。如果您改为问“实现以下模式是否是个好主意?”那么也许答案会有所不同。然而,这也更像是一个见仁见智的问题,因此可能不适合 SO。
  • @AlexanderSolovets:一个可能的错误是将foo返回给调用者。由于它看起来像一个指针,很容易与malloc返回的指针混淆...我不是说你一定会犯这个错误,但我不会容忍这种做法,因为不太懂行的程序员会被咬太容易了。这是一个 hack,可能会解决一个特定的问题,但我不能称之为 good practice
【解决方案2】:

是的,隐藏数据是个好习惯。

作为alloca(foo_size); 模式的替代方案,可以声明对齐的字符数组并执行指针转换。但是,指针转换不是完全可移植的。如果大小由变量而不是编译时常量定义,则字符数组需要是 VLA:

extern size_t size;

struct sfoo;

#include <stddef.h>

int main(void) {
  unsigned char _Alignas (max_align_t) cptr[size];
  // or unsigned char _Alignas (_Complex  long double) cptr[size];  // some widest type
  struct sfoo *sfooptr = (struct sfoo *) cptr;
  ...

如果 VLA 不需要或不可用,请将大小声明为常量 (#define foo_N 100),保证至少满足需要。

【讨论】:

  • 这个想法很有趣,但算不上好的实践。
  • @chqrlie 如果您详细说明了不好的做法问题,也许我们可以解决它们。 “很难说是好的做法。”类似于 OP 说“代码不起作用”。
  • 什么是良好实践可能是基于意见的。如果隐藏实现所付出的代价是这个难以阅读的 VLA 装置,我个人认为不值得这么麻烦。你提出的是一个允许堆栈分配的黑客,你打算用宏隐藏它吗?这可以被认为是良好做法吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-06
  • 1970-01-01
  • 1970-01-01
  • 2017-10-11
  • 2021-10-30
相关资源
最近更新 更多