printf具有无与伦比的格式和参数

我正在尝试理解printf函数。

我知道在我读到这个函数之后,c编译器会自动将所有小于int的参数(如chars和short)转换为int。 我也知道long long int(8个字节)没有被转换并按原样推送到堆栈。

所以我写了这个简单的c代码:

#include  int main() { long long int a = 0x4444444443434343LL; // note that 0x44444444 is 4 times 0x44 which is D in ascii. // and 0x43434343 is 4 times 0x43 which is C in ascii. printf("%c %c\n", a); return 0; } 

创建一个大小为8字节的变量并将其推送到堆栈。 我也知道printf循环遍历格式字符串,当它看到%c时,它会将指针递增4(因为它知道char被转换为int – 示例如下)

类似于:char c =(char)va_arg(list,int) – >

(*(int *)((pointer + = sizeof(int)) – sizeof(int)))

正如您所看到的,当指针指向它时会获得4个字节,并将其递增4

我的问题是:

在我的逻辑中,它应该打印在小端机器CD上这不是发生了什么,我问为什么? 我相信你们中的一些人比我更了解实施情况,这就是为什么我问他的问题。

编辑:实际结果是C跟着它有一些垃圾字符。

我知道有些人可能会说它的未定义行为 – 它实际上取决于实现,我只是想知道实现的逻辑。

您的逻辑将解释70年代和80年代早期C编译器的行为。 较新的ABI使用各种方法将参数传递给函数,包括变量参数函数。 您必须研究系统ABI以了解在您的情况下如何传递参数,从具有显式未定义行为的构造推断无效。

顺便说一下,短于int类型不会被转换或转换,它们会被提升为 int 。 请注意, float值在传递给变量参数函数时会转换为double 。 根据ABI传递非整数类型和大于int整数类型,这意味着它们可以在常规寄存器或甚至特殊寄存器中传递,而不必在堆栈中传递。

printf依赖于定义的宏来隐藏这些实现细节,因此可以以可移植的方式编写具有不同ABI和不同标准类型大小的体系结构。

评论显示,这里存在一个根本性的误解

根据这里的格式字符串,编译器应该知道推送了4个字节,将4个字节转换为char并打印出来…

但问题在于没有规则说C使用单个字节寻址堆栈来处理所有事情

不同的处理器体系结构可以 – 并且确实 – 使用各种技术将参数传递给函数。 一些参数可以在传统堆栈上传递,但是其他参数可以在寄存器中传递,或者通过其他技术传递。 不同类型的参数可以在不同类型的寄存器中传递(32对64位,整数对浮点等)。

显然,C编译器必须知道如何正确传递它正在编译的平台的参数。 显然,必须仔细编写像printf这样的可变函数,以便根据它所使用的平台正确获取其变量参数。 但是像%d这样的格式说明符不重复,只是意味着“从栈中弹出4个字节并将它们视为int ”。 类似地, %c并不意味着“弹出4个字节并将结果整数打印为字符”。 当printf遇到格式说明符%c%d ,它需要安排获取int类型的下一个参数,无论它做什么 。 事实上,如果调用代码实际传递的下一个参数不是 int类型 – 例如,如果在这里,下一个参数实际上是long long int类型 – 那么通常无法知道什么可能发生。

具体来说,当printf刚看到%d%c说明符时,它在内部的作用相当于调用

 va_arg(argp, int) 

字面上说,“获取int类型的下一个参数”。 然后它实际上取决于va_arg的作者(以及在声明的其余函数和宏)来确切地知道在这个特定平台上获取int类型的下一个参数需要什么。

显然,有可能知道在特定平台上实际会发生什么。 (显然va_arg的作者必须知道!)但是你不会根据C语言本身,或者猜测你认为应该发生什么来弄清楚它。 您将不得不阅读ABI – 应用程序二进制接口 – 它指定了平台上函数调用约定的详细信息。 这些细节很难找到,因为很少有程序员真正关心它们。

我说“必须仔细编写printf以正确获取其变量参数”,但实际上我稍微错过了,因为正如我后面所说,“实际上由va_arg的作者确切地知道它需要什么”。 你是对的,可以编写一个合理的便携式printf实现。 C FAQ列表中有一个例子。

如果您想了解有关函数调用约定的更多信息,另外一个有趣的主题是外部函数接口或FFI。 (例如,还有另一个库libffi可以帮助你 – 可移植! – 执行一些涉及操作函数参数的更奇特的任务。)

笔记类型太多了

C指定11个整数类型signed charchar ,… unsigned long long作为不同类型。 除了char必须匹配signed charunsigned char ,这些可以实现为10种不同的编码或仅2(对所有人使用64位有符号或无符号)。 标准库为每个标准库都有一个printf()说明符。(由于子升级,还有其他问题)。

到目前为止还没有真正的问

然而,C还有许多其他类型printf()说明符:

 ju uintmax_t jd intmax_t zu size_t td ptrdiff_t PRIdLEASTN int_leastN_t where N is 8, 16, 32, 64 PRIuLEASTN uint_leastN_t PRIdN intN_ PRIuN uintN_t Many others 

通常,这些附加类型可以与上述11 不同 或兼容

任何时候代码在printf()使用这些其他类型 ,都会出现不同/兼容的问题,并阻止许多编译器检测/提供最佳的建议匹配打印说明符。

1存在各种条件/限制。