C 或 C++ 链接器中是否有任何类型检查?
Is there any type checking in C or C++ linkers?
我说链接器不检查函数参数是否正确。它们不检查函数调用的数量或类型,也不检查全局数据引用的类型。这对所有链接器都是如此吗?
我在 x86-64 上使用面向 Linux 的 Clang。链接器是否检查引用是否在正确的段中?或者就链接器而言,外部引用实际上只是一个 void * 吗?
我来自高级语言背景 C# 和 Scala,所以对于那些沉浸在低级世界中的人来说,这似乎是显而易见的。我在汇编程序中编写了几个函数(系统调用),我注意到在汇编程序中没有外部函数的参数原型。
上下文:我实际上是在写一个编译器。目前我的目标是使用汇编函数进行系统调用的预处理 C .i 文件,但替代方案是 C++、汇编程序甚至机器代码,所以我试图权衡成本和收益,尤其是类型检查,汇编器/编译器/链接器我可以用来检查我自己的程序及其函数原型生成的正确性。
在 c++ 中,所有编译器都实现某种形式的名称重整来分离重载函数;然而,由于 return 类型不包含在 mangle 中(通常),这里可能存在同样的问题。
在 C 中,您是正确的 - 链接器无法检查,但这确实不是您想象的那么严重的问题。请记住,编译器已经检查过对函数的调用是否与提供的头文件匹配,因此导致问题的唯一方法是将头文件的不同重复版本编译成两个不同的 c 文件,这些文件稍后被链接。
这很难意外地做到(尽管如果你做到了,你可能最终会出现一些非常微妙的错误)。
正如@Yakk 所解释的,函数可以根据它们的参数进行重载,因此编译器会生成包含参数及其类型信息的错位函数名称。 linker 大多只是检查符号名称和大小,但由于 mangling 确保函数的名称不同,不匹配的参数不会 link.
函数 return 类型 不是重整的一部分(因为重载 return 类型是不合法的),所以如果你声明 int test()
在一个翻译单元中调用 float test()
,在另一个翻译单元中调用 float test()
,linker 不会捕捉到它,你会得到不好的结果。
类似地,全局变量的类型(和 classes 的静态成员等等)不会被 linker 检查,所以如果你声明 extern int test;
一个翻译单元并在另一个翻译单元中定义 float test;
,你会得到不好的结果。
在某些情况下,linker 可以比较两个不同翻译单元中符号的 大小,并且可以通过这种方式发现一些问题。
实际上,这在正常的 C++ 开发中很少成为问题,因为每当 >1 个翻译单元需要函数或变量或 class 时,您将在头文件中声明它,该头文件包含在两个翻译单元和编译器都会在 linker 运行之前捕获任何错误。 (可能会出现问题的一个例子是,如果您使用的是外部二进制库,而您拥有的头文件与该库不匹配。)
许多 linker 包含提供某种程度的类型检查的功能,但细节各不相同。一些编译器会在使用一种调用约定的函数名称前加上下划线,但在使用另一种调用约定的函数名称中省略下划线;如果一个翻译单元使用一种约定声明函数,但实际函数是使用另一种约定定义的,则该程序将在 link 次被拒绝。
一些平台(例如 PIC 的 HiTech C)允许编译器或汇编语言程序在声明或引用符号时指定 16 位值,如果引用点提供的值不符合定义。 C 编译器根据参数类型和 return 类型的组合为每个函数生成一个哈希值,如果尝试调用签名哈希值不同的函数,linker 将会发出尖叫声在定义和调用站点。
我说链接器不检查函数参数是否正确。它们不检查函数调用的数量或类型,也不检查全局数据引用的类型。这对所有链接器都是如此吗?
我在 x86-64 上使用面向 Linux 的 Clang。链接器是否检查引用是否在正确的段中?或者就链接器而言,外部引用实际上只是一个 void * 吗?
我来自高级语言背景 C# 和 Scala,所以对于那些沉浸在低级世界中的人来说,这似乎是显而易见的。我在汇编程序中编写了几个函数(系统调用),我注意到在汇编程序中没有外部函数的参数原型。
上下文:我实际上是在写一个编译器。目前我的目标是使用汇编函数进行系统调用的预处理 C .i 文件,但替代方案是 C++、汇编程序甚至机器代码,所以我试图权衡成本和收益,尤其是类型检查,汇编器/编译器/链接器我可以用来检查我自己的程序及其函数原型生成的正确性。
在 c++ 中,所有编译器都实现某种形式的名称重整来分离重载函数;然而,由于 return 类型不包含在 mangle 中(通常),这里可能存在同样的问题。
在 C 中,您是正确的 - 链接器无法检查,但这确实不是您想象的那么严重的问题。请记住,编译器已经检查过对函数的调用是否与提供的头文件匹配,因此导致问题的唯一方法是将头文件的不同重复版本编译成两个不同的 c 文件,这些文件稍后被链接。
这很难意外地做到(尽管如果你做到了,你可能最终会出现一些非常微妙的错误)。
正如@Yakk 所解释的,函数可以根据它们的参数进行重载,因此编译器会生成包含参数及其类型信息的错位函数名称。 linker 大多只是检查符号名称和大小,但由于 mangling 确保函数的名称不同,不匹配的参数不会 link.
函数 return 类型 不是重整的一部分(因为重载 return 类型是不合法的),所以如果你声明 int test()
在一个翻译单元中调用 float test()
,在另一个翻译单元中调用 float test()
,linker 不会捕捉到它,你会得到不好的结果。
类似地,全局变量的类型(和 classes 的静态成员等等)不会被 linker 检查,所以如果你声明 extern int test;
一个翻译单元并在另一个翻译单元中定义 float test;
,你会得到不好的结果。
在某些情况下,linker 可以比较两个不同翻译单元中符号的 大小,并且可以通过这种方式发现一些问题。
实际上,这在正常的 C++ 开发中很少成为问题,因为每当 >1 个翻译单元需要函数或变量或 class 时,您将在头文件中声明它,该头文件包含在两个翻译单元和编译器都会在 linker 运行之前捕获任何错误。 (可能会出现问题的一个例子是,如果您使用的是外部二进制库,而您拥有的头文件与该库不匹配。)
许多 linker 包含提供某种程度的类型检查的功能,但细节各不相同。一些编译器会在使用一种调用约定的函数名称前加上下划线,但在使用另一种调用约定的函数名称中省略下划线;如果一个翻译单元使用一种约定声明函数,但实际函数是使用另一种约定定义的,则该程序将在 link 次被拒绝。
一些平台(例如 PIC 的 HiTech C)允许编译器或汇编语言程序在声明或引用符号时指定 16 位值,如果引用点提供的值不符合定义。 C 编译器根据参数类型和 return 类型的组合为每个函数生成一个哈希值,如果尝试调用签名哈希值不同的函数,linker 将会发出尖叫声在定义和调用站点。