为啥 ANSI C 没有命名空间?
Posted
技术标签:
【中文标题】为啥 ANSI C 没有命名空间?【英文标题】:Why doesn't ANSI C have namespaces?为什么 ANSI C 没有命名空间? 【发布时间】:2011-05-22 17:18:42 【问题描述】:对于大多数语言来说,拥有命名空间似乎是轻而易举的事。但据我所知,ANSI C 不支持它。为什么不?是否有计划将其包含在未来的标准中?
【问题讨论】:
使用 C++ 作为 C-with-namespace! 我当然可以,但我还是想知道 2 件事。不必要的独特语法:所有其他具有命名空间的语言都只使用“。”作为分隔符,因为它与“。”的其他用途没有歧义。而且,更关键的是,c++ 从未引入范围内的 using 指令。这意味着程序员过度使用 using 指令将命名空间导入全局范围。这意味着 c++ 标准委员会现在不能向 std:: 添加新功能,因为会破坏的代码量导致分区冗余。 @Chris Becke:我喜欢独特的语法。我想知道我是在查看名称空间中的类还是类中的成员。 @ChrisBecke,这已经晚了几年,但有趣的是你认为 C++ 命名空间的实现很差,所以它们不应该在 C 中实现。然后你注意到其他语言在没有实现它们的情况下实现了它们C++的挂断。如果其他语言可以做到,为什么不将它们引入 C 语言? 【参考方案1】:只是历史原因。当时没有人想到有像命名空间这样的东西。他们也真的试图保持语言简单。他们将来可能会拥有它
【讨论】:
未来标准委员会是否有为 C 添加命名空间的动议?迁移到 C/C++ 模块可能会在未来变得更容易吗? @lanoxx 由于向后兼容的原因,没有向 C 添加命名空间的意愿。【参考方案2】:ANSI C 是在命名空间之前发明的。
【讨论】:
是吗?第一个 ANSI C 规范是 1989 年。我很确定命名空间(以某种形式或另一种形式)在此之前就在编程语言中。例如,Ada 在 1983 年被标准化,并将包作为命名空间。这些反过来又基本上基于 Modula-2 模块。 我不会把 ANSI C 的发明追溯到它的规范被正式采用的时候。该语言事先存在,规范只是记录了已经存在的内容。尽管从本网站上的一些答案来看,人们可能会认为规范是第一位的,而第一个编译器是事后才想到的。 ANSI C 确实与前 ANSI C 有一些显着差异,但命名空间不是其中之一。 同时,我在 2020 年写它,在命名空间出现之后。最新的 C 标准仍然没有它们。尽管 C 很有意义,但这是一个非常缺失的功能。【参考方案3】:不是答案,但不是评论。 C 没有提供明确定义namespace
的方法。它具有可变范围。例如:
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
,也不能导入from mylib
。
【讨论】:
你也不能用namespace mylib void init(); void say_hello();
替换最后两行,这也很重要(ish)。【参考方案4】:
C 确实有命名空间。一种用于结构标签,一种用于其他类型。考虑以下定义:
struct foo
int a;
;
typedef struct bar
int a;
foo;
第一个有tag foo,后一个用typedef做成类型foo。仍然没有发生名称冲突。这是因为结构标签和类型(内置类型和 typedef 的类型)存在于不同的命名空间中。
C 不允许随意创建 new 命名空间。 C 在它被认为在语言中很重要之前已经标准化,并且添加名称空间也会威胁向后兼容性,因为它需要名称修改才能正常工作。我认为这可以归因于技术问题,而不是哲学。
编辑: JeremyP 幸运地纠正了我并提到了我错过的命名空间。标签和结构/联合成员也有命名空间。
【讨论】:
名字空间其实不止两个。除了你提到的两个之外,每个结构和联合的成员还有标签的名称空间和名称空间。 @JeremyP:非常感谢您的更正。我只是在记忆中写下了这个,我没有检查标准:-) 函数的命名空间怎么样? 这很可能被称为命名空间,但我相信这些不是 OP 所询问的那种命名空间。 @jterm 不。我不是在提倡破解 C 特性,只是陈述事实。每个struct
定义为其成员声明一个新的命名空间。我不提倡利用这一事实,我也不知道有任何利用它的方法,因为struct
s 不能拥有静态成员。【参考方案5】:
从历史上看,C 编译器不会修改名称(它们在 Windows 上会,但 cdecl
调用约定的修改只包括添加下划线前缀)。
这使得使用其他语言(包括汇编程序)的 C 库变得容易,这也是您经常看到 C++ API 的 extern "C"
包装器的原因之一。
【讨论】:
但是为什么会出现这样的问题呢?我的意思是,假设所有命名空间的名称都以 _da13cd6447244ab9a30027d3d0a08903 开头,然后是名称(这是我刚刚生成的 UUID v4)?这可能会破坏使用此特定 UUID 的名称,但这种可能性基本上为零。因此,在实践中处理 only_namespace_names 不会有问题。 @einpoklum 不是。懒惰的程序员是这里唯一的问题。名称修改是绝对垃圾,就像它的实现一样是这种懒惰的标志。太糟糕了,当他们通过适当的处理很容易变得懒惰时。想象一下,如果“名称修改”而不是借口是一个实际功能,并且库myvectors
的模块 vector
中的函数 add
导致符号 myvectors_vector_add
,这就是每个使用 C 库的人将普遍使用而不会出现问题,而在 C 中它只是 vector::add
或等效的。我也无法想象。【参考方案6】:
C 有命名空间。语法为namespace_name
。您甚至可以将它们嵌套在general_specific_name
中。如果您希望能够在不每次都写出命名空间名称的情况下访问名称,请将相关的预处理器宏包含在头文件中,例如
#define myfunction mylib_myfunction
这比名称修改和某些语言承诺提供命名空间的其他暴行要干净得多。
【讨论】:
我的看法不同。使语法复杂化,在符号上引入名称修饰等以实现对预处理器已经微不足道的事情,我称之为肮脏的黑客和糟糕的设计。 我看不出你怎么能真正支持这个立场。当每个其他系统都有不同的本土黑客来实现命名空间时,请向 javascript 社区询问有关集成项目的问题。我从未听过有人抱怨“命名空间”或“包”关键字给他们的语言增加了太多的复杂性。另一方面,尝试调试充斥着宏的代码会很快变得棘手! @R.. 如果 C++ 中的名称修改被标准化,就不会发生这种情况。仅此一项对 ABI 兼容性没有帮助,但肯定会解决名称映射问题。 这不是命名空间,这是使用命名约定来模仿命名空间给你的东西。 C 人居然会面无表情地争论这个,这让我感到很震惊。 C++ 中有很多特性很锋利,让人不寒而栗。命名空间不是这些功能之一。他们很棒,他们工作得很好。为了记录,预处理器没有什么是微不足道的。最后,去除名称是微不足道的,有很多命令行实用程序可以为您完成。【参考方案7】:为了完整起见,有几种方法可以在 C 中实现您可能从命名空间中获得的“好处”。
我最喜欢的方法之一是使用一个结构来容纳一堆方法指针,它们是您的库/等的接口。
然后你使用这个结构的外部实例,你在你的库中初始化它指向你所有的函数。这允许您在库中保持简单的名称,而无需踩到客户端命名空间(除了全局范围的 extern 变量,1 个变量与可能数百个方法..)
涉及一些额外的维护,但我觉得它很少。
这是一个例子:
/* interface.h */
struct library
const int some_value;
void (*method1)(void);
void (*method2)(int);
/* ... */
;
extern const struct library Library;
/* 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 */
使用 .语法在经典 Library_function() Library_some_value 方法上创建了强关联。但是有一些限制,因为您不能将宏用作函数。
【讨论】:
... 并且编译器是否足够聪明,可以在您执行library.method1()
时在编译时“取消引用”函数指针?
这太棒了。我可能要补充的一件事是,我正在尝试将我的 .c
文件中的所有函数默认设为静态,因此唯一公开的函数是在 .c
文件中的 const struct
定义中明确公开的函数。
这是个好主意,但是你如何处理常量和枚举呢?
@einpoklum - 很抱歉 necro,但至少从 6.3.0 版开始,gcc 将在同时使用 -O2
和 -flto
进行编译时计算 function1
/method2
的实际地址.除非您将此类库与您自己的源代码一起编译,否则这种方法会为其函数调用增加一些开销。
@AlexReinking:嗯,这很好,但我们永远不会内联这些函数。而且 - 坏死很棒,不需要道歉。【参考方案8】:
因为想要将此功能添加到 C 中的人们还没有聚集在一起并组织起来对编译器作者团队和 ISO 机构施加一些压力。
【讨论】:
我认为我们将看到 C 中的命名空间只有当这些人自己组织起来并创建一个支持命名空间的扩展时。那么 ISO 机构将别无选择,只能将它们作为标准发布(或多或少的变化)。 javascript(在这方面与 C 有一些相似之处)就是这样做的。 @themihai: "create an extension" = 让 gcc 和 clang 人编译命名空间。【参考方案9】:C 不支持 C++ 等命名空间。 C++ 命名空间的实现会破坏名称。下面概述的方法允许您在 C++ 中使用名称空间的好处,同时具有未损坏的名称。我意识到问题的本质是为什么 C 不支持命名空间(一个简单的答案是它不支持,因为它没有实现:))。我只是认为它可能会帮助别人了解我是如何实现模板和命名空间的功能的。
我写了一篇关于如何使用 C 来利用命名空间和/或模板的教程。
Namespaces and templates in C
Namespaces and templates in C (using Linked Lists)
对于基本命名空间,可以简单地作为命名空间名称的前缀作为约定。
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
使用模板文件如下
乘法模板.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
我在提供的链接上创建了一个更详细的教程,展示了它如何与链接列表一起工作。希望这可以帮助某人!
【讨论】:
您的链接解释了如何添加命名空间。但是,问题是为什么不支持命名空间。所以这个答案不是答案,应该是评论。【参考方案10】:你可以。像其他人的答案一样,在结构中定义函数指针。
但是,在你的 header 文件中声明它,将它标记为 static const 并使用相应的函数对其进行初始化。 使用 -O1 或更高版本,它将被优化为正常的函数调用
例如:
void myfunc(void);
static const struct
void(*myfunc)(void);
mylib =
.myfunc = myfunc
;
利用#include 语句,您无需在一个标头中定义所有函数。
不要不要添加标题保护,因为你包含它不止一次。
例如: 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"
;
【讨论】:
以上是关于为啥 ANSI C 没有命名空间?的主要内容,如果未能解决你的问题,请参考以下文章