printf 对参数的顺序敏感吗?

Is printf sensitive to the order of arguments?

我想在 c++

中使用 printf 打印一行
    int t0 = time(NULL);
    int outIndx = 99736;
    printf ("time using recurision = %d secs, the result is = %d\n",(time(NULL) - t0), outIndx);

在这种情况下,printf 的输出是:

使用递归的时间 = 0 秒,结果为 = 0

但是当颠倒outIndx(time(NULL) - t0)的顺序时:

printf ("time using recurision = %d secs, the result is = %d\n", outIndx,(time(NULL) - t0));

printf 的输出被正确打印:

使用递归的时间 = 99736 秒,结果为 = 0

为什么会这样?

time returns time_t 不必与 int 大小相同。在这种情况下,%d 不是正确的输出格式。

原问题:"Is printf sensitive to the order of arguments?"

函数参数的顺序在标准中没有定义,而是由编译器使用的调用约定决定的。假设您正在使用 cdecl 调用约定(许多 C 编译器将其用于 x86 体系结构),其中函数中的参数从 从右到左计算。

举个例子

int i=5;
    printf("%d%d%d%d%d%d",i++,i--,++i,--i,i);
Output :45545

这是因为参数是从右到左求值的。

但是,在您的示例中,您尝试打印类型为 "time_t" 的时间,如下所示:

typedef __time_t time_t;

time_t 数据类型是 ISO C 库中定义用于存储系统时间值的数据类型。这些值从标准 time() 库函数返回。此类型是标准 header 中定义的 typedef。 ISO C 将 time_t 定义为算术类型,但未为其指定任何特定类型、范围、分辨率或编码。同样未指定的是应用于时间值的算术运算的含义。

是否以整数形式实现取决于底层架构。就你的例子而言,我相信它至少没有实现为 "unsigned int " (int t0),所以结果是 implementation-defined.

A C 溶液

一般来说,显示time_t is to break down its components to a struct tm using gmtime() or localtime() and display those or convert them as desired with strftime(), or ctime()的值的方式是直接从time_t到显示本地时间的字符串。

如果出于某种目的想要查看原始值,C 标准规定 time_t 是实数,这意味着它是整数或浮点数 (C 2011 (N1570) 6.2.5 17) .因此,您应该使用 difftime() 将时差转换为双倍:

#include <ctime>
#include <cstdio>

void process()
{
    static unsigned dummy = 0;
    for (size_t i = 0 ; i < 1000000000 ; ++i)
    {
        dummy += (dummy + i) / (dummy * i + 1);
    }
}

int main()
{
    const time_t t0 = time(NULL);
    process(); // let say it can take up to a few hours
    const time_t t_end = time(NULL);

    struct tm breakdown_time = { 0 };
    breakdown_time.tm_sec = difftime(t_end, t0);
    (void) mktime(&breakdown_time);
    printf("duration: %.2d:%.2d:%.2d\n", breakdown_time.tm_hour, breakdown_time.tm_min, breakdown_time.tm_sec);
    // Output: "duration: 00:00:08"
}

Demo

C++ 解决方案

但是您不是在使用 C++ 吗? std::clock() 提供了一种计算 CPU 持续时间的方法(CPU 花费的时间,不包括它在其他线程上花费的时间):

#include <iostream>
#include <iomanip>

void process()
{
    static unsigned dummy = 0;
    for (size_t i = 0 ; i < 1000000000 ; ++i)
    {
        dummy += (dummy + i) / (dummy * i + 1);
    }
}

int main() {
    const std::clock_t t0 = std::clock();
    process(); // business code that take time to run
    const std::clock_t t_end = std::clock();
    const double duration = static_cast<double>(t_end - t0) / CLOCKS_PER_SEC;
    std::cout << "duration: " << std::setprecision(3) << duration << " s" << std::endl;
    // Output: "duration: 8.92 s"
}

Demo

C++14 解决方案

我偶然发现了一种计算实际持续时间的优雅、通用的方法,它在 C++14 中,并使用了 std::chrono 中的所有高级工具:

#include <iostream>
#include <iomanip>
#include <chrono>

void process()
{
    static unsigned dummy = 0;
    for (size_t i = 0 ; i < 100000000 ; ++i)
    {
        dummy += (dummy + i) / (dummy * i + 1);
    }
}

template<typename TimeT = std::chrono::milliseconds>
struct measure
{
    template<typename F, typename ...Args>
    static typename TimeT::rep execution(F&& func, Args&&... args)
    {
        auto start = std::chrono::system_clock::now();
        std::forward<decltype(func)>(func)(std::forward<Args>(args)...);
        auto duration = std::chrono::duration_cast< TimeT> 
                            (std::chrono::system_clock::now() - start);
        return duration.count();
    }
};

int main() {
    std::cout << "duration: " << measure<>::execution(process) << " ms" << std::endl;
    // Output: "duration: 707 ms"
}

Demo

传递给 printf(可变参数函数)的所有参数在调用时被压入堆栈。 printf 例程根据第一个参数中连续提供的 % 标识符弹出参数。取决于机器和编译器,参数在堆栈上的顺序(从上到下或从下到上)可能会有所不同。

在你的例子中,当你首先传递 (time(NULL) - t0) 时,因为它是 time_t 类型并且它的大小与 printf 通过查看 %d 解释的不同。它会产生问题。

现在为什么当您将 (time(NULL) - t0) 作为第二个参数传递时它会起作用:

同样的逻辑,假设 timt_t 在你的情况下是 8 个字节,而 int 是 4 个字节。对于每个 %d printf 将从参数堆栈中消耗 4 个字节。因此,当您首先打印 outIdx(int) 时,printf 从对应于 outIdx 的堆栈中获取正确的 4 个字节。当 printf 看到第二个 %d 时,它将使用 4 个下一个字节,这是 time_t 对象的 8 个字节的一部分。并且根据 time_t 的值、系统的字节顺序,print 将从堆栈中打印 4 个字节(共 8 个字节),假设它是 int。

当 print 提供了错误的 % 标识符时,您最终可能会弄乱其堆栈排列,并可能会出现不正确或未定义的行为。