1

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

%[[:]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;
}
4

1 回答 1

1

您的示例中指出的一个问题是它们执行未定义的行为。terminfo的定义行为使用显式参数标记,例如%p1将推送参数传递到堆栈,它们可以由诸如%d. 缺少这一点,您将依赖 ncurses 的 termcap 解决方法(它没有参数标记),并且副手,这将使表达式像

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

尝试从参数列表中读取多个参数。您的示例给出了一个,因此您处于C 语言未定义行为的领域(即,可变长度参数列表中的参数数量错误)。如果您的调用传递了额外的参数,那可能没问题(例如,请参阅Visual C 接受错误数量的参数?),但如果使用较少的参数,结果可能是访问参数列表之外的内存。

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

进一步阅读:

    /*
     * 分析字符串,看看我们需要多少可变参数列表中的参数,
     * 以及它们的类型是什么。我们只会接受字符串参数,如果他们
     * 在显式参数引用之后显示为 %l 或 %s 格式(例如,
     * %p2%s)。所有其他参数都是数字。
     *
     * 'number' 粗略计算我们在字符串中看到的 pop 的数量,并且
     * 'popcount' 显示字符串中的最高参数编号。我们想
     * 简单地使用后一个计数,但如果我们正在读取 termcap 字符串,则
     * 可能是我们看不到显式参数编号的情况。
     */

以及诸如tc_BUMP之类的功能,这些功能可以适应 termcap 缺少参数标记的情况。

于 2017-11-19T03:33:26.733 回答