sprintf的编码错误是指什么情况下会返回-1?

9

我知道 snprintf"发生编码错误"时会返回一个负值。

但是,什么是产生这种结果的"编码错误"的简单示例呢?

我正在使用 gcc 10.2.0 C编译器,并尝试使用格式说明符不正确、字段长度过大甚至空格式字符串。

  • 格式说明符不正确会直接打印出来
  • 长度说明符过大会导致致命错误
  • 空格式字符串也会导致致命错误

这与反复执行类似以下操作有关:

length += snprintf(...

构建格式化字符串。

如果确定不会返回负值,这可能是安全的。

通过负长度来推进缓冲区指针可能导致越界。但我正在寻找实际上会发生这种情况的情况。如果有这样的情况,那么增加此功能的复杂性可能是合理的:

length += result = snprintf(...

到目前为止,我还没有找到一个值得增加复杂性以检查编译器可能永远不会产生的值的场景。也许你可以举一个简单的例子。


1
例如,在字符串参数中存在无效的UTF-8字节序列(假设使用UTF-8执行字符集)。 - dxiv
当 n 的值大于 {INT_MAX} 或者需要容纳输出的字节数(不包括终止空字符)大于 {INT_MAX} 时,它可以返回负值。 - Shawn
此外,编译器并不重要;实现snprintf的是系统libc。 - Shawn
@Shawn 当我尝试使用 long n=((long)INT_MAX)*2L 时,它产生了致命错误,而不是返回 -1。 - Ted Shaneyfelt
2个回答

5
什么是 sprintf 的编码错误,会返回 -1?
在我的机器上,使用 "%ls" 格式化字符串时,无法处理 0xFFFF,这显然是一个编码错误。
  char buf[42];
  wchar_t s[] = { 0xFFFF,49,50,51,0 };
  int i = snprintf(buf, sizeof buf, "<%ls>", s);
  printf("%d\n", i);

输出

-1

以下代码返回-1,但原因并不是由于编码错误,而是由于病态格式所致。
#include <stdio.h>

int main() {
  size_t n = 0xFFFFFFFFLLu + 1;
  char *fmt = malloc(n);
  if (fmt == NULL) {
    puts("OOM");
    return -42;
  }
  memset(fmt, 'x', n);
  fmt[n - 1] = '\0';
  char buf[42];
  int i = snprintf(buf, sizeof buf, fmt);
  printf("%d %x\n", i, (unsigned) i);
  free(fmt);
  return 7;
}

输出

-1 ffffffff

当传递一个太大的大小时,即使 snprintf() 只需要6个字节,我仍然得到了一个出乎意料的-1。

  char buf[42];
  int i = snprintf(buf, 4299195472, "Hello");
  printf("%d\n", i);

输出

-1

我想到了一个简短的例子,会在使用*fprintf()将内容输出至stdout时返回-1,这是由于方向冲突所导致的。

#include <wchar.h>
#include <stdio.h>

int main() {
  int w = wprintf(L"Hello wide world\n");
  wprintf(L"%d\n", w);
  int s = printf("Hello world\n");
  wprintf(L"%d\n", s);
}

输出

Hello wide world
17
-1

我在Windows 10上使用gcc MSYS2 C,在Eclipse或cmd shell中都没有输出,结果相同。在安装了gdb并在Eclipse下运行时,它在snprintf处崩溃并永远无法返回。 - Ted Shaneyfelt
@TedShaneyfelt 这里有几个代码片段。所有4个片段都失效了吗? - chux - Reinstate Monica
snprintf代码片段的结果:2、崩溃、5。我不需要wprintf或printf。它们不能直接放在一起,但可以用块包装起来,在没有刷新的情况下没有输出,并且如预期的那样在第二个块中崩溃。 wprintf和printf是不同的函数,我对它们不感兴趣,也不询问它们,因此我没有在最终的示例程序中烦扰它们。 - Ted Shaneyfelt
@TedShaneyfelt 第一行代码 char buf[42]; wchar_t s[] = { 0xFFFF,49,50,51,0 }; 对你有什么作用?接下来的代码 int i = snprintf(buf, sizeof buf, "<%ls>", s); printf("%d\n", i); 又是做什么的呢? - chux - Reinstate Monica
输出数字2 - Ted Shaneyfelt

3
通常情况下,只有在发生输出错误时,您才会从printf和类似函数中得到错误。来自Linux man页面的描述:
如果遇到输出错误,则返回负值。
因此,如果您输出到FILE并出现某种类型的输出错误(EPIPE,EIO),则会获得负返回值。对于s[n]printf而言,由于没有输出,因此永远不会有负返回值。
标准讨论了可能出现“编码错误”的可能性,但仅定义了在宽字符流方面的含义,并指出字节流在某些情况下可能需要转换为宽流。
如果呈现给底层mbrtowc函数的字符序列不形成有效的(广义)多字节字符,或者传递给底层wcrtomb的代码值不对应于有效的(广义)多字节字符,则会发生编码错误。宽字符输入/输出函数和字节输入/输出函数仅在发生编码错误时将EILSEQ宏的值存储在errno中。
这似乎意味着,如果使用%ls或%lc格式将宽字符串或字符转换为字节,则可能会出现编码错误。不确定是否还有其他情况可能会出现编码错误。

是的,如果它没有特别提到可能出现编码错误的可能性,我就不会费心去查找。有趣的是,我发现的那些没有返回正数或零的错误根本没有返回。也许如果它们能够捕获这些致命错误并返回-1,那么问题就解决了。如果只是这样,那么在提供一个没有虚假%*值的常量格式字符串时,似乎没有理由检查结果是否为负数。 - Ted Shaneyfelt

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