printf:这个安全吗?

5
我想知道这个表达是否安全:
int main (void)
{
  char my_tab[256];

  memset(my_tab,0x61,sizeof(my_tab));

  printf("Is it safe ? : %.256s",my_tab); /* is it safe ? */
}

1
如果0x61对应于一个“安全”字符(我建议使用'a'代替),那么它是安全的 - 并非所有计算机都使用ASCII :) - pmg
你是对的,我只是使用0x61来得到与'\0'不同的东西。 - Joze
2
为了确保安全,您需要 #include <stdio.h>#include <string.h> - pmg
此外,为了更加安全,您可能需要在要打印的字符串后添加一个换行符进行终止:printf("Is it safe ? : %.256s\n", my_tab);;并且,假设该代码可以使用C90编译器进行编译,请在最终的 } 之前添加 return 0; - pmg
3个回答

7

是的,你将打印出256个字符,没有更多了。

根据C11标准7.21.6. p4:

可选精度指定d、i、o、u、x和X转换中要显示的最小数字位数,指定a、A、e、E、f和F转换中小数点字符后要显示的数字位数,指定g和G转换中要显示的最大有效数字位数,或者指定s转换中要写入的最大字节数。精度采用句点(.)后跟一个星号*(稍后描述)或一个可选十进制整数的形式;如果只指定了句点,则精度为零。如果任何其他转换说明符都出现了精度,则行为未定义。

根据7.21.6.1. p8:

s: 如果没有l长度修饰符,则参数必须是指向字符类型数组的初始元素的指针。从该数组中写入字符,直到(但不包括)终止空字符。如果指定了精度,则写入不超过那么多个字节。如果未指定精度或精度大于数组大小,则该数组应包含空字符。


我并不完全相信。假设实现使用 strlen() 函数来确定字符串的长度?所以它应该是安全的,但也可能不是。 - JeremyP
4
如果我的实现是这样的,那么我会要求退款! - ams
@JeremyP:这个问题是关于 printf() 而不是 strlen(),对吧? - alk
2
@JeremyP 这是有关 s 的标准规定:如果没有 l 长度修改符,则参数应为指向字符类型数组初始元素的指针。从该数组中提取的字符将被写入,直到(但不包括)遇到终止空字符为止。如果指定了精度,则最多只会写入那么多字节。如果未指定精度或精度大于数组大小,则该数组必须包含一个空字符。 - 2501
1
因此,在我看来,如果你有精度,那么空终止符是不需要的。 - 2501
显示剩余3条评论

2

这是安全的。

来自printf(3) - Linux手册页面http://man7.org/linux/man-pages/man3/printf.3.html

   s      If no l modifier is present: The const char * argument is
          expected to be a pointer to an array of character type
          (pointer to a string).  Characters from the array are written
          up to (but not including) a terminating null byte ('\0'); if a
          precision is specified, no more than the number specified are
          written.  If a precision is given, no null byte need be
          present; if the precision is not specified, or is greater than
          the size of the array, the array must contain a terminating
          null byte.

函数 vsnprintf/lib/vsprintf.c 中调用 strnlen(s, spec.precision) 来获取将要格式化的字符串长度:

/**
 * strnlen - Find the length of a length-limited string
 * @s: The string to be sized
 * @count: The maximum number of bytes to search
 */
size_t strnlen(const char *s, size_t count)
{
    const char *sc;

    for (sc = s; count-- && *sc != '\0'; ++sc)
        /* nothing */;
    return sc - s;
}

只有有效的字节才会被访问。

static noinline_for_stack
char *string(char *buf, char *end, const char *s, struct printf_spec spec)
{
    int len, i;

    if ((unsigned long)s < PAGE_SIZE)
        s = "(null)";

    len = strnlen(s, spec.precision);

    if (!(spec.flags & LEFT)) {
        while (len < spec.field_width--) {
            if (buf < end)
                *buf = ' ';
            ++buf;
        }
    }
    for (i = 0; i < len; ++i) {
        if (buf < end)
            *buf = *s;
        ++buf; ++s;
    }
    while (len < spec.field_width--) {
        if (buf < end)
            *buf = ' ';
        ++buf;
    }

    return buf;
}

特定实现的 printfvsnprintfstrnlen 不会因为参数不指向以 null 结尾的字符串而出错,这只与该特定实现有关。重要的是 C 标准要求它是安全的。特定实现的 strlen 可能在给定空指针参数时表现良好,但 strlen(NULL) 仍然具有未定义行为。 - Keith Thompson

1

是的,这应该是安全的。试图访问缓冲区末尾之后的字符的实现应被视为无效。

处理%.Ns的伪代码,其中N是一个数字,应该如下所示:

size_t count = 0;
size_t N = ...;
char *ptr = <next-arg>;
while (count < N && *ptr != '\0') {
    putchar(*ptr++);
    count++;
}

请注意,上述代码永远不会引用超过N之后的字符。
可以想象一种实现方式是反转while循环的条件,这将访问缓冲区末尾的字节。但是,这样的实现是无效的,因为根据标准,只有在未指定大小或大小大于您传递的字符数时,才允许实现要求空终止符:

[7.19.6.1.8] 如果未指定精度或精度大于数组的大小,则该数组应包含一个空字符。


"output_to_buffer"有一个很好的标准名称:putchar。 :-) - R.. GitHub STOP HELPING ICE
@R.. 谢谢!我想我也应该替换掉我的<next-arg>。不过这将不再是伪代码了 :-) - Sergey Kalinichenko

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接