为什么ANSI C没有命名空间?

129

对于大多数编程语言而言,命名空间似乎都是一件易如反掌的事情。但据我所知,ANSI C 并不支持它。为什么呢?有计划在未来的标准中包含它吗?


17
使用C++时,可以将其视为带有命名空间的C语言! - Khaled Alshaya
8
好的,我可以翻译。请提供需要翻译的句子或段落。 - Pulkit Sinha
6
两件事。一个不必要的独特语法:所有其他具有名称空间的语言都使用'.'作为分隔符,因为它不会与其他用途的'.'引起歧义。更为关键的是,c++从未引入过带作用域的using指令。这意味着程序员过度使用using指令将名称空间导入全局作用域。这意味着c++标准委员会现在无法向std::添加新功能,因为由此导致的代码量已经使分区变得多余。 - Chris Becke
2
@Chris Becke:我喜欢独特的语法。我想知道我是在查看名称空间中的类还是类中的成员。 - JeremyP
6
@ChrisBecke,虽然有些晚了,但你认为C++的命名空间实现不好,因此它们不应该在C中实现。然后你提到其他语言实现它们时没有遇到C++那样的麻烦。如果其他语言能够做到,为什么不把它们引入到C中呢? - weberc2
显示剩余3条评论
10个回答

136

为了完整性,在C语言中有几种实现“名称空间”所提供的好处的方法。

我最喜欢的方法之一是使用一个结构体来存储一堆指向你的库/等等接口的方法指针。

然后,你可以使用一个外部实例来初始化这个结构体,在你的库中指向所有的函数。这样可以使你在库中保持名称简单,而不会影响客户端的名称空间(除了全局范围的外部变量,1个变量与可能数百个方法相比更容易处理)。

当然,这种方法需要进行额外的维护,但我认为它是很小的。

以下是一个示例:

/* interface.h */

struct library {
    const int some_value;
    void (*method1)(void);
    void (*method2)(int);
    /* ... */
};

extern const struct library Library;
/* end interface.h */

/* interface.c */
#include "interface.h"

void method1(void)
{
   ...
}
void method2(int arg)
{
   ...
}

const struct library Library = {
    .method1 = method1,
    .method2 = method2,
    .some_value = 36
};
/* end interface.c */

/* client code */
#include "interface.h"

int main(void)
{
    Library.method1();
    Library.method2(5);
    printf("%d\n", Library.some_value);
    return 0;
}
/* end client code */

使用.语法可以在经典的Library_function()Library_some_value方法之间建立强关联。但是,这种语法存在一些限制,例如您无法将宏用作函数。


23
编译器在你执行 library.method1() 时是否足够聪明,能够在编译时解除函数指针的引用? - einpoklum
2
这太棒了。我要补充的一件事是,我正在尝试将所有函数默认设为静态函数放在我的.c文件中,因此只有在.c文件中显式公开的函数才会被公开。 - lastmjs
3
好的,如何处理常量和枚举类型呢? - nowox
4
抱歉打扰,但至少在6.3.0版本中,使用-O2-flto编译时,gcc将计算出function1/method2的实际地址。除非您将这些库与自己的源代码一起编译,否则此方法会增加其函数调用的开销。 - Alex Reinking
6
@AlexReinking:好的,但我们永远不会让这些函数内联。而且,“necro'ing”很棒,无需道歉。 - einpoklum
显示剩余8条评论

87

C 有命名空间。其中一个用于结构体标签,另一个用于其他类型。考虑以下定义:

struct foo
{
    int a;
};

typedef struct bar
{
    int a;
} foo;

第一个有tag foo,后者是通过typedef变成类型foo。仍然没有名称冲突发生。这是因为结构标签和类型(内置类型和typedef的类型)存在不同的命名空间。

C语言不允许按意愿创建新的命名空间。在语言被标准化之前,这被认为并不重要。增加命名空间也会威胁到向后兼容性,因为它需要正确工作的名称编码。我认为这可以归因于技术上的细节,而不是哲学上的问题。

编辑: JeremyP幸运地纠正了我并提到了我错过的命名空间。标签和struct / union成员也有命名空间。


14
实际上,有超过两个的命名空间。除了您提到的两个之外,还有一个用于标签的命名空间,以及每个结构体和联合体成员的命名空间。 - JeremyP
1
@JeremyP:非常感谢您的纠正。我只是凭记忆写的,没有查看标准 :-) - Mads Elvheim
3
函数的命名空间怎么办? - themihai
14
这可能被称为命名空间,但我认为这些不是提问者所询问的那种命名空间。 - avl_sweden
1
@jterm 不,我并不是在提倡黑客攻击C语言的特性,只是陈述事实。每个“struct”定义都为其成员声明了一个新的命名空间。我并不主张利用这一事实,也不知道任何可以利用它的手段,因为“struct”不能有静态成员。 - JeremyP
显示剩余3条评论

27

C具有命名空间。语法为namespace_name。您甚至可以像general_specific_name那样嵌套它们。如果想要在不每次编写命名空间名称的情况下访问名称,可以在头文件中包含相关的预处理器宏,例如:

#define myfunction mylib_myfunction

这比命名混淆和某些语言为了提供命名空间而犯下的其他罪行要干净得多。


33
我有不同的看法。通过在语法上增加复杂性,引入符号名称混淆等方法来实现预处理器已经轻松解决的问题,我认为这是一种肮脏的技巧和糟糕的设计。 - R.. GitHub STOP HELPING ICE
53
我不认为你真正支持那个立场。请向Javascript社区咨询如何将项目集成,因为每个其他系统都有自己独特的实现名称空间的方法。我从未听说过有人抱怨“命名空间”或“包”关键字会给他们的语言增加太多复杂性。另一方面,试图调试布满宏的代码可能会很麻烦! - weberc2
6
我听过很多人抱怨C++的名称重整(从调试、工具链、ABI兼容性、动态符号查找等方面)以及不知道特定名称实际上指的是什么的复杂性。 - R.. GitHub STOP HELPING ICE
15
这不是命名空间,而是使用命名约定来模拟命名空间的功能,但效果很差。 - Claudiu
34
我觉得真是让人难以置信,C语言的人居然会一本正经地争辩这个问题。在C++中,有很多功能具有尖锐的边缘,让人们感到烦恼。但命名空间不是其中之一,它们非常好用,能够很好地发挥作用。并且,对于预处理器来说,没有什么是微不足道的。最后,名称解析很简单,有很多命令行实用程序可以帮你实现它。 - Nir Friedman
显示剩余13条评论

16

历史上,C编译器不会修饰函数名(在Windows上会进行修饰,但针对cdecl调用约定的修饰只涉及添加下划线前缀)。

这使得从其他语言(包括汇编语言)使用C库变得容易,并且这也是为什么你经常会在C++ API中看到extern "C"的包装器之一的原因。


3
为什么这会成为一个问题?我的意思是,假设所有的命名空间名称都以__da13cd6447244ab9a30027d3d0a08903_和名称(这是我刚生成的UUID v4)开头?这可能会破坏使用此特定UUID的名称,但这种情况几乎为零。因此,在实践中_mangling_only_namespace_names_不会有问题。 - einpoklum
@einpoklum 不是。懒惰的程序员才是问题所在。名称混淆极其糟糕,就像它的实现一样,表明了这种懒惰。太糟糕了,当他们可以通过正确混淆名称来轻松偷懒时。想象一下,如果“名称混淆”不是借口而是一个实际功能,那么库“myvectors”的模块“vector”中的函数“add”将导致一个符号“myvectors_vector_add”,而每个使用C库的人都会普遍使用它而没有问题,在C中只需使用vector::add或等效方法即可。我也无法想象。 - Kaihaku

9

只是出于历史原因,那个时候没有人考虑过像命名空间这样的东西。此外,他们真的试图保持语言简单。也许将来会有。


3
标准委员会中是否有任何动向,将来会在 C 语言中添加命名空间?随着移动到 C/C++ 模块可能会使这在未来更容易? - lanoxx
1
@lanoxx 由于向后兼容性的原因,C语言没有添加命名空间的意愿。 - themihai

7

虽然不是答案,但也不是评论。C语言没有提供显式定义命名空间的方法,它具有变量作用域。例如:

int i=10;

struct ex {
  int i;
}

void foo() {
  int i=0;
}

void bar() {
  int i=5;
  foo();
  printf("my i=%d\n", i);
}

void foobar() {
  foo();
  bar();
  printf("my i=%d\n", i);
}

您可以使用合格名称来表示变量和函数:
mylib.h

void mylib_init();
void mylib_sayhello();

与命名空间的唯一区别是您不能使用 using,也不能从 mylib 导入。

你也不能用 namespace mylib { void init(); void say_hello(); } 替换最后两行,这也是重要的(有点)。 - einpoklum

6

ANSI C在命名空间出现之前就被发明了。


13
第一个 ANSI C 规范出现在1989年,我相信在此之前某种形式的名称空间已经存在于编程语言中。例如,1983年标准化的 Ada 语言就将包作为名称空间。这些包本质上是基于 Modula-2 模块构建的。 - JUST MY correct OPINION
5
我不认为ANSI C的发明应该归功于其规范被正式采纳的时期;语言早在此之前就已经存在,规范只是记录了已经存在的内容。尽管从这个网站上的一些答案来看,人们可能会认为规范先于第一个编译器而产生。 - Crashworks
ANSI C与之前的C语言确实存在一些显著差异,但名称空间不是其中之一。 - dan04
同时,我是在2020年编写的,这已经是命名空间出现之后了。最新的C标准仍然没有它们。尽管C很有意义,但这是一个非常缺失的功能。 - user14222280

2
因为希望将这种功能添加到C语言中的人们没有聚集在一起组织,对编译器作者团队和ISO机构施加压力。

1
我认为只有当这些人组织起来并创建支持命名空间的扩展时,我们才会在C中看到命名空间的出现。然后,ISO机构将别无选择,只能将其作为标准发布(带有更多或更少的更改)。这就是JavaScript(在这方面与C有一些相似之处)的做法。 - themihai
4
@themihai: "create an extension" = 让gcc和clang的开发人员编译名称空间。 - einpoklum

2
C语言不像C++那样支持命名空间。C++命名空间的实现会破坏名称。下面概述的方法允许您在C++中获得命名空间的好处,同时具有未被破坏的名称。我知道问题的本质是为什么C不支持命名空间(一个简单的答案是它没有被实现:))。我只是想说这可能会帮助某些人了解如何实现模板和命名空间的功能。
我撰写了一篇关于如何使用C获得命名空间和/或模板优势的教程。 C语言中的命名空间和模板 使用链表的C语言中的命名空间和模板 对于基本的命名空间,可以简单地将命名空间名称作为约定前缀。
namespace MY_OBJECT {
  struct HANDLE;
  HANDLE *init();
  void destroy(HANDLE * & h);

  void do_something(HANDLE *h, ... );
}

可以写作:
struct MY_OBJECT_HANDLE;
struct MY_OBJECT_HANDLE *my_object_init();
void my_object_destroy( MY_OBJECT_HANDLE * & h );

void my_object_do_something(MY_OBJECT_HANDLE *h, ... );

一种我所需要的第二种方法是使用命名空间和模板概念,使用宏合并和包含。例如,我可以创建一个

template<T> T multiply<T>( T x, T y ) { return x*y }

使用模板文件如下所示

multiply-template.h

_multiply_type_ _multiply_(multiply)( _multiply_type_ x, _multiply_type_ y);

multiply-template.c

_multiply_type_ _multiply_(multiply)( _multiply_type_ x, _multiply_type_ y) {
  return x*y;
}

现在我们可以定义int_multiply函数如下。在这个例子中,我将创建一个int_multiply.h/.c文件。

int_multiply.h

#ifndef _INT_MULTIPLY_H
#define _INT_MULTIPLY_H

#ifdef _multiply_
#undef _multiply_
#endif
#define _multiply_(NAME) int ## _ ## NAME 

#ifdef _multiply_type_
#undef _multiply_type_
#endif
#define _multiply_type_ int 

#include "multiply-template.h" 
#endif

int_multiply.c

#include "int_multiply.h"
#include "multiply-template.c"

在所有这些结束时,您将拥有用于的功能和头文件。
int int_multiply( int x, int y ) { return x * y }

我在提供的链接上创建了一个更详细的教程,展示了它如何与链表一起工作。希望这能帮助到某些人!

3
您的链接解释了如何添加命名空间。然而,问题是为什么不支持命名空间,所以这个答案并不是一个答案,应该改为评论。 - Thomas Weller
@ThomasWeller 你说得没错,但我真的看不出所有这些信息怎么能放进一条评论里。对我来说,它现在就很有价值,如果缩短成一条评论,它会失去意义。 - Binarus

1
你可以在结构体中定义函数指针,就像其他答案所述。
然而,你需要在头文件中声明它,标记为静态常量,并用相应的函数初始化。使用 -O1 或更高级别编译参数时,它将被优化成普通函数调用。
例如:
void myfunc(void);
    
static const struct {
      void(*myfunc)(void);
} mylib = {
      .myfunc = myfunc
};

利用 #include 语句,这样你就不需要在一个单一的头文件中定义所有函数。
不要添加头文件保护,因为你会多次包含它。

eg: header1.h

#ifdef LIB_FUNC_DECL
void func1(void);
#elif defined(LIB_STRUCT_DECL)
struct {
      void(*func)(void);
} submodule1;
#else
    .submodule1.func = func1,
#endif

mylib.h

#define LIB_FUNC_DECL
#include "header1.h"
#undef LIB_FUNC_DECL
#define LIB_STRUCT_DECL

static const struct {
#include "header1.h"
#undef LIB_STRUCT_DECL
} mylib = {
    #include "header1.h"
};

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