terminfo 参数化字符串 %d 编码行为

terminfo parameterized string %d encoding behavior

我试图了解 %d 编码在 terminfo 的参数化字符串解析器中的行为。相关的手册页是 here 并指出 -

%[[:]flags][width[.precision]][doxXs]
        as  in  printf, flags are [-+#] and space.  Use a ":" to allow the
        next character to be a "-" flag, avoiding interpreting "%-" as  an
        operator.

但没有说明从何处打印值以及如何处理边缘情况。它们是来自堆栈还是来自传递给参数化字符串的参数?另外,当传递额外的参数(参数化字符串中不等于 %d)或存在额外的 %d(不正确的参数化字符串?)时会发生什么?那个未定义的行为或实现定义或由某处定义定义?

我试图通过手动编写一些有效和无效的字符串并验证输出来检查某些情况,但到目前为止一切都有些不一致,所以我在这里看不到模式 -

#include <iostream>
#include <curses.h>
#include <term.h>

using namespace std;

int main() {
    // single %d prints single argument
    // => 2
    auto res = tparm("[%d]", 2);
    cout << res << endl;

    // single %d prints single argument and ignores additional
    // => 2
    res = tparm("[%d]", 2, 3, 4);
    cout << res << endl;

    // multiple %d prints 0 for absent additional arguments
    // => 2-0-0-0
    res = tparm("[%d-%d-%d-%d]", 2);
    cout << res << endl;

    // multiple %d prints with equal number of arguments prints
    // first two correctly and rest 0
    // => 2-3-0-0-0
    res = tparm("[%d-%d-%d-%d-%d]", 2,3,4,5,6);
    cout << res << endl;

    // single value pushed to stack prints from stack
    // => 2
    res = tparm("[%p1%d]", 2);
    cout << res << endl;

    // single value pushed to stack prints from stack and ignores extra arguments
    // => 2
    res = tparm("[%p1%d]", 2,3,4);
    cout << res << endl;

    // single value pushed to stack prints from stack and additional prints are 0
    // if no arguments are provided
    // => 2-0-0
    res = tparm("[%p1%d-%d-%d]", 2);
    cout << res << endl;

    // single value pushed to stack prints from stack and additional prints 0
    // even if equal arguments are provided
    // => 2-0-0
    res = tparm("[%p1%d-%d-%d]", 2,3,4);
    cout << res << endl;

    // single value pushed to stack prints from stack after pop()?
    // => 100-<garbage>
    res = tparm("[%p1%d-%c]", 100);
    cout << res << endl;

    // pushed to stack via {} and equal arguments provided, prints all
    // => 2-1-100-200
    res = tparm("[%{1}%{2}%d-%d-%d-%d]", 100, 200);
    cout << res << endl;

    // pushed to stack via {} and %p1 equal arguments provided
    // prints only stack and rest 0
    // => 100-2-1-0
    res = tparm("[%{1}%{2}%p1%d-%d-%d-%d]", 100, 200);
    cout << res << endl;
}

您的示例中指出的一个问题是它们执行未定义的行为。 terminfo 的 defined 行为使用 %p1 等显式参数标记将推送参数传递到堆栈,在那里它们可以由 %d 等运算符弹出。缺少它,您将依赖 ncurses 的 termcap 变通方法(它没有参数标记),并且副手,这将使表达式像

    res = tparm("[%d-%d-%d-%d]", 2);

试图从参数列表中读取多个参数。您的示例给出了 one,因此您处于 C 语言 未定义行为的领域(即,可变长度参数中的参数数量错误列表)。如果您的调用传递了额外的参数,那么 可能 没问题(参见示例 Visual C accepting wrong number of arguments?),但如果更少,结果可能是访问 外部的内存参数列表。

回复评论: ncurses 允许在没有 %p1 的情况下使用 %d 的 termcap 样式。但它会计算参数的数量,在进行实际替换之前列出这些参数。由于它将这些作为可变长度参数列表处理,因此它无法确定您的应用程序是否为给定字符串传递了错误数量的参数。

延伸阅读:

    /*
     * Analyze the string to see how many parameters we need from the varargs list,
     * and what their types are.  We will only accept string parameters if they
     * appear as a %l or %s format following an explicit parameter reference (e.g.,
     * %p2%s).  All other parameters are numbers.
     *
     * 'number' counts coarsely the number of pop's we see in the string, and
     * 'popcount' shows the highest parameter number in the string.  We would like
     * to simply use the latter count, but if we are reading termcap strings, there
     * may be cases that we cannot see the explicit parameter numbers.
     */

以及 tc_BUMP 等功能,这些功能可解决 termcap 缺少参数标记的问题。