我正在尝试理解terminfo的参数化字符串解析器中%d
编码的行为。相关的手册页在这里,并指出-
%[[:]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的定义的行为使用显式参数令牌(如%p1
)将推送参数传递到堆栈上,在堆栈中可以由运算符(如%d
)弹出推送参数。由于缺乏这一点,您将依赖于ncurses的termcap解决方案(它没有参数标记),而这将立即生成类似的表达式
res = tparm("[%d-%d-%d-%d]", 2);
尝试从参数列表中读取多个参数。您的示例给出了一个,因此您处于C语言未定义行为的领域(即,可变长度参数列表中的参数数量错误)。如果您的调用传递了额外的参数,则可能可以(例如,请参阅Visual C接受错误数量的参数?),但如果传递的参数较少,则结果可能是访问参数列表之外的内存。
回应评论:ncurses允许在没有%p1
的情况下使用%d
。但它会计算参数的数量,在进行实际替换之前列出这些参数。由于它将这些参数作为可变长度的参数列表进行处理,因此无法确定应用程序是否为给定字符串传递了错误数量的参数。
进一步阅读:
tiparm
、tparm
和tputs
等,诅咒数据库终端的接口tparm
的源,例如
/**分析字符串以查看我们需要从varargs列表中获得多少参数,*以及它们的类型。只有当字符串参数*在显式参数引用(例如。,*%p2%s)。所有其他参数都是数字。**"number"粗略地计算我们在字符串中看到的pop的数量,并且*"popcount"显示字符串中的最高参数编号。我们希望*简单地使用后一个计数,但如果我们正在读取termcap字符串*可能是我们看不到显式参数数字的情况。*/
以及tc_BUMP等功能,这些功能适应了termcap缺少参数令牌的情况。