【问题标题】:C library naming conventionsC 库命名约定
【发布时间】:2010-01-01 14:27:27
【问题描述】:

简介

大家好,我最近学会了 C 编程! (这对我来说是一个巨大的进步,因为 C++ 是第一门语言,我接触并吓跑了我近 10 年。)来自主要是 OO 背景(Java + C#),这是一个非常好的范式转变。

我喜欢 C。它是如此美丽的语言。最让我惊讶的是,C 支持的高级模块化和代码可重用性 - 当然它没有 OO 语言那么高,但仍然远远超出我对命令式语言的期望。

问题

如何防止客户端代码和我的 C 库代码之间的命名冲突? 在 Java 中有包,在 C# 中有命名空间。想象一下,我编写了一个 C 库,它提供了“添加”操作。客户端很可能已经使用了这样的操作 - 我该怎么办?

我特别在寻找对客户友好的解决方案。例如,我根本不想给我所有的 api 操作加上前缀,比如“myuniquelibname_add”。在 C 世界中有哪些常见的解决方案?是不是把所有的api操作都放在一个struct中,让客户端自己选择前缀?

我非常期待通过您的回答获得的见解!

编辑(修改后的问题)

尊敬的回答者,感谢您的回答!我现在明白了,前缀是安全避免命名冲突的唯一方法。所以,我想修改我的问题:我有什么可能,让客户选择自己的前缀?

Unwind 发布的答案是一种方式。它不使用通常意义上的前缀,但必须在每个 api 调用前加上“api->”。还有哪些进一步的解决方案(例如使用#define)?

编辑 2(状态更新)

这一切都归结为以下两种方法之一:

  • 使用结构
  • 使用#define(注意:方法有很多种,如何使用#define来实现,我想要的)

我不会接受任何答案,因为我认为没有正确答案。选择的解决方案取决于具体情况和自己的偏好。我一个人将尝试您提到的所有方法,以找出在哪种情况下最适合我的方法。随意在相应答案的 cmets 中发表支持或反对某些方法的论据。

最后,我要特别感谢:

  • Unwind - 他的复杂回答包括“结构方法”的完整实现
  • Christoph - 为他的好回答和指向我Namespaces in C
  • 所有其他人 - 为您提供宝贵意见

如果有人认为结束这个问题是合适的(因为没有进一步的见解可以期待),他/她应该可以随意这样做 - 我不能决定这个,因为我不是 C 大师。

【问题讨论】:

    标签: c naming-conventions


    【解决方案1】:

    我不是 C 大师,但在我使用过的库中,使用前缀来分隔函数是很常见的。

    例如,SDL 将使用 SDL,OpenGL 将使用 gl,等等...

    【讨论】:

    • 在最近编写了一个相当大的 C 库之后,并尝试使用其中一种替代方法,我终于回到了一个简单的前缀。
    【解决方案2】:

    Ken 提到的结构方式如下所示:

    struct MyCoolApi
    {
      int (*add)(int x, int y);
    };
    
    MyCoolApi * my_cool_api_initialize(void);
    

    然后客户会这样做:

    #include <stdio.h>
    #include <stdlib.h>
    
    #include "mycoolapi.h"
    
    int main(void)
    {
      struct MyCoolApi *api;
    
      if((api = my_cool_api_initialize()) != NULL)
      {
        int sum = api->add(3, 39);
    
        printf("The cool API considers 3 + 39 to be %d\n", sum);
      }
      return EXIT_SUCCESS;
    }
    

    这仍然存在“命名空间问题”; struct 名称(称为“结构标记”)必须是唯一的,并且您不能声明本身有用的嵌套结构。不过,它非常适合收集函数,并且是您在 C 中经常看到的一种技术。

    更新:这是实现方面的外观,这是在评论中要求的:

    #include "mycoolapi.h"
    
    /* Note: This does **not** pollute the global namespace,
     * since the function is static.
    */
    static int add(int x, int y)
    {
      return x + y;
    }
    
    struct MyCoolApi * my_cool_api_initialize(void)
    {
      /* Since we don't need to do anything at initialize,
       * just keep a const struct ready and return it.
      */
      static const struct MyCoolApi the_api = {
        add
      };
    
      return &the_api;
    }
    

    【讨论】:

    • @unwind:感谢您的见解。我看到了与此解决方案及其局限性有关的额外工作。虽然好处是,客户可以选择自己的“前缀”(在您的示例中为“api”)。 2个问题:你能提供“my_cool_api_initialize”的实现吗?还有其他方法可以让客户选择自己的前缀(使用#define)吗?
    • 为什么要在运行时初始化api结构?只需在my_cool_api_initialize() 中使用一个初始化器,并在其中创建结构const,以便具有链接时优化的编译器可以内联函数指针;您甚至可能希望在 .h 文件中定义该函数,以使即使对于没有链接时优化的编译器也能做到这一点...
    • @Christoph:我有点通用;也许 API 需要在运行时做一些事情。我把它砍掉了,现在实现更简单了。
    • @unwind:感谢您的努力。
    • 为什么add函数的声明使用void:void (*add)(int x, int y);与使用int的实现static int add(int x, int y)不同? @unwind
    【解决方案3】:

    很遗憾你被 C++ 吓跑了,因为它有命名空间来处理这个问题。在 C 语言中,您几乎只能使用前缀——您当然不能“将 api 操作放入结构中”。

    编辑:在回答您关于允许用户指定自己的前缀的第二个问题时,我会像瘟疫一样避免它。 99.9% 的用户会对你提供的任何前缀感到满意(假设它不是太傻),并且在他们必须跳过以满足剩余的 0.1% 的箍(宏、结构等)时会非常不高兴。

    【讨论】:

    • 为什么不喜欢使用结构体作为命名空间?只需制作一个函数指针结构,它看起来与静态类方法非常相似。这会破坏一堆优化,但也给了你更多的控制权并打开了有趣的状态管理选项。
    • 关于 Windows 中的所有这些。 COM 接口。
    • 我必须承认,我认为 OP 是在询问 SQLite 或 ncurses 等“普通旧”C 库。另外,我做了一些 Windows 编程,但从不使用 COM,所以我猜不是“关于所有这些”。
    • @Neil:感谢您编辑您的答案。我很欣赏你的意见
    • 既然你提到了 SQLite,我认为指出 SQLite DOES 正是这样做的! sqlite.org/c3ref/io_methods.htmlsqlite.org/c3ref/vfs.html
    【解决方案4】:

    作为库用户,您可以通过预处理器轻松定义自己的缩短命名空间;结果看起来有点奇怪,但它确实有效:

    #define ns(NAME) my_cool_namespace_ ## NAME
    

    让写作成为可能

    ns(foo)(42)
    

    而不是

    my_cool_namespace_foo(42)
    

    作为库作者,您可以提供缩写名称as desribed here

    如果您遵循unwinds's advice 并创建API 结构,您应该使函数指针编译时常量以使inlinig 成为可能,即在您的.h 文件中,使用以下代码:

    // canonical name
    extern int my_cool_api_add(int x, int y);
    
    // API structure
    struct my_cool_api
    {
        int (*add)(int x, int y);
    };
    
    typedef const struct my_cool_api *MyCoolApi;
    
    // define in header to make inlining possible
    static MyCoolApi my_cool_api_initialize(void)
    {
        static const struct my_cool_api the_api = { my_cool_api_add };
        return &the_api;
    }
    

    【讨论】:

    • 为什么你可以在_initialize()函数中返回一个指向局部变量的指针?
    • @ijustlovemath: 因为变量有静态存储时长
    【解决方案5】:

    不幸的是,在 C 中没有避免名称冲突的可靠方法。由于它缺少名称空间,因此您只能在全局函数和变量的名称前面加上前缀。大多数库选择一些短而“独特”的前缀(unique 出于显而易见的原因放在引号中),并希望不会发生冲突。

    需要注意的一点是,库的大部分代码都可以静态声明——这意味着它不会与其他文件中类似命名的函数发生冲突。但是导出的函数确实必须小心前缀。

    【讨论】:

      【解决方案6】:

      由于您要公开具有相同名称的函数,因此客户端不能包含您的库头文件以及其他具有名称冲突的头文件。在这种情况下,您在函数原型之前的头文件中添加以下内容,这也不会影响客户端的使用。

      #define add myuniquelibname_add
      

      请注意,这是一个快速修复解决方案,应该是最后的选择。

      【讨论】:

      • 我不想在所有公开的 api 操作前加上一个“唯一”前缀,然后让客户端执行(例如):#define $ myuniquelibname,这样他就可以执行类似“$ _add(...)”,而不是“myunique.._add(...)”?另外:为什么你认为这是最后一个选项?
      • @Dave: $_add(...) 不起作用,因为标识符不是 C 令牌。我将在库头文件中使用#define 而不是客户端。从可用性方面,客户端希望通过调用 add() 添加功能,客户端不想考虑符号冲突。通过包含适当的头文件,应该调用库中的 add 函数。因为我们希望客户端只使用一个头文件,并确保不包含来自两个名称冲突的库的头文件。这总是会导致将来出现一些维护问题。
      【解决方案7】:

      对于 struct 方法的一个非常大的例子,看看 Linux 内核;这种风格的 C 语言有 30 多万行。

      【讨论】:

      • linux 不使用它来解决名称冲突问题,它用于复制虚拟函数和类似的东西。例如,VFS 基于具有函数指针的结构,这些函数指针对于每种文件系统类型都不同。
      【解决方案8】:

      前缀是 C 级别的唯一选择。

      在某些平台(支持链接器的单独命名空间,如 Windows、OS X 和一些商业 unice,但不支持 Linux 和 FreeBSD)上,您可以通过将代码填充到库中来解决冲突,并且仅从您的库中导出符号确实需要。 (例如,如果导出符号中存在冲突,则在 importlib 中使用别名)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-12-13
        • 1970-01-01
        • 1970-01-01
        • 2011-11-19
        • 2015-11-21
        • 1970-01-01
        相关资源
        最近更新 更多