如何规避 GCC 中的格式截断警告?
How to circumvent format-truncation warning in GCC?
我收到以下 gcc 格式截断警告:
test.c:8:33: warning: ‘/input’ directive output may be truncated writing 6 bytes into a region of size between 1 and 20 [-Wformat-truncation=]
snprintf(dst, sizeof(dst), "%s-more", src);
^~~~~~
test.c:8:3: note: ‘snprintf’ output between 7 and 26 bytes into a destination of size 20
snprintf(dst, sizeof(dst), "%s-more", src);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
像这样的代码:
char dst[20];
char src[20];
scanf("%s", src);
snprintf(dst, sizeof(dst), "%s-more", src);
printf("%s\n", dst);
我知道它可能会被截断 - 但这正是我首先使用 snprintf 的原因。有没有办法让编译器清楚这是有意的(不使用 pragma 或 -Wno-format-truncation)?
- 警告是在gcc7.1中添加的,见gcc7.1 release changes。
- 来自gcc docs:
Level 1 of -Wformat-truncation [...] warns only about calls to bounded functions whose return value is unused and that will most likely result in output truncation.
- 问题是 bug report 并已作为注意事项关闭:
Unhandled output truncation is typically a bug in the program. [...]
In cases when truncation is expected the caller typically checks the return value from the function and handles it somehow (e.g., by branching on it). In those cases, the warning is not issued. The source line printed by the warning suggests that this is not one of those cases. The warning is doing what it was designed to do.
- 但我们可以只检查 snprintf 的 return 值,return 错误时为负值。
#include <stdio.h>
#include <stdlib.h>
int main() {
char dst[2], src[2] = "a";
// snprintf(dst, sizeof(dst), "%s!", src); // warns
int ret = snprintf(dst, sizeof(dst), "%s!", src);
if (ret < 0) {
abort();
}
// But don't we love confusing one liners?
for (int ret = snprintf(dst, sizeof(dst), "%s!", src); ret < 0;) exit(ret);
// Can we do better?
snprintf(dst, sizeof(dst), "%s!", src) < 0 ? abort() : (void)0;
// Don't we love obfuscation?
#define snprintf_nowarn(...) (snprintf(__VA_ARGS__) < 0 ? abort() : (void)0)
snprintf_nowarn(dst, sizeof(dst), "%s!", src);
}
在 https://godbolt.org/ 上使用 gcc7.1 gcc7.2 gcc7.3 gcc8.1 和 -O{0,1,2,3} -Wall -Wextra -pedantic
进行了测试。不给出任何警告。 gcc8.1 optimizes/removes 对 abort()
的调用优化大于 -O1
.
奇怪的是,当编译为C++源文件时,即使我们检查return值,警告仍然存在。在 C 中一切都很好。在 C++ 中更喜欢 std::format_to
anyway。所以:
- 我们可以只使用编译器特定的语法来禁用警告。
#include <stdio.h>
#include <stdlib.h>
int main() {
char dst[2];
char src[2] = "a";
// does not warn in C
// warns in C++ with g++ newer than 10.1 with optimization -O2
int ret = snprintf(dst, sizeof(dst), "%s!", src);
if (ret < 0) {
abort();
}
// does not warn in C
// still warns in C++
ret = snprintf(dst, sizeof(dst), "%s!", "a");
if (ret < 0) {
abort();
}
// use compiler specific pragmas to disable the warning
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-truncation"
snprintf(dst, sizeof(dst), "%s!", "a");
#pragma GCC diagnostic pop
// wrapper macro with compiler specific pragmas
// works for any gcc
// works from g++ 10.1
#ifndef __GNUC__
#define snprintf_nowarn snprintf
#else
#define snprintf_nowarn(...) __extension__({ \
_Pragma("GCC diagnostic push"); \
_Pragma("GCC diagnostic ignored \"-Wformat-truncation\""); \
const int _snprintf_nowarn = snprintf(__VA_ARGS__); \
_Pragma("GCC diagnostic pop"); \
_snprintf_nowarn; \
})
#endif
snprintf_nowarn(dst, sizeof(dst), "%s!", "a");
}
此错误仅在调用 length-limited *printf
函数时触发(例如 snprintf
、vsnprintf
)。换句话说,它 而不是 表明您可能正在溢出缓冲区,就像 sprintf 可能发生的那样;它只通知您您没有检查 snprintf
是否正在执行其工作和截断。 (旁注:snprintf
总是 null-terminates,所以这不会导致 non-terminated 字符串。)
知道这一点后,我对使用 -Wno-format-truncation
全局禁用它更加乐观,而不是试图哄骗 gcc
忽略特定实例。
这个页面对我很有用:
https://www.fluentcpp.com/2019/08/30/how-to-disable-a-warning-in-cpp/
您可以通过执行以下操作解决 gcc/clang 编译器的问题:
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-truncation"
snprintf(dst, sizeof(dst), "%s-more", src);
#pragma GCC diagnostic pop
上面的网页也有针对Visual Studio个编译器警告的解决方案。
此外,为目标大小引入一个 volatile
临时变量也是一种解决方法。
char dst[20];
char src[20];
volatile int dst_size = sizeof(dst);
snprintf(dst, dst_size, "%s-more", src);
根据 Martin Sebor 的建议,您也可以使用此 snprintf_trunc
宏;
#define snprintf_trunc(dst, size, ...) \
do { \
volatile size_t n = size; \
snprintf (dst, n, __VA_ARGS__); \
} while (0)
我收到以下 gcc 格式截断警告:
test.c:8:33: warning: ‘/input’ directive output may be truncated writing 6 bytes into a region of size between 1 and 20 [-Wformat-truncation=]
snprintf(dst, sizeof(dst), "%s-more", src);
^~~~~~
test.c:8:3: note: ‘snprintf’ output between 7 and 26 bytes into a destination of size 20
snprintf(dst, sizeof(dst), "%s-more", src);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
像这样的代码:
char dst[20];
char src[20];
scanf("%s", src);
snprintf(dst, sizeof(dst), "%s-more", src);
printf("%s\n", dst);
我知道它可能会被截断 - 但这正是我首先使用 snprintf 的原因。有没有办法让编译器清楚这是有意的(不使用 pragma 或 -Wno-format-truncation)?
- 警告是在gcc7.1中添加的,见gcc7.1 release changes。
- 来自gcc docs:
Level 1 of -Wformat-truncation [...] warns only about calls to bounded functions whose return value is unused and that will most likely result in output truncation.
- 问题是 bug report 并已作为注意事项关闭:
Unhandled output truncation is typically a bug in the program. [...]
In cases when truncation is expected the caller typically checks the return value from the function and handles it somehow (e.g., by branching on it). In those cases, the warning is not issued. The source line printed by the warning suggests that this is not one of those cases. The warning is doing what it was designed to do.
- 但我们可以只检查 snprintf 的 return 值,return 错误时为负值。
#include <stdio.h>
#include <stdlib.h>
int main() {
char dst[2], src[2] = "a";
// snprintf(dst, sizeof(dst), "%s!", src); // warns
int ret = snprintf(dst, sizeof(dst), "%s!", src);
if (ret < 0) {
abort();
}
// But don't we love confusing one liners?
for (int ret = snprintf(dst, sizeof(dst), "%s!", src); ret < 0;) exit(ret);
// Can we do better?
snprintf(dst, sizeof(dst), "%s!", src) < 0 ? abort() : (void)0;
// Don't we love obfuscation?
#define snprintf_nowarn(...) (snprintf(__VA_ARGS__) < 0 ? abort() : (void)0)
snprintf_nowarn(dst, sizeof(dst), "%s!", src);
}
在 https://godbolt.org/ 上使用 gcc7.1 gcc7.2 gcc7.3 gcc8.1 和 -O{0,1,2,3} -Wall -Wextra -pedantic
进行了测试。不给出任何警告。 gcc8.1 optimizes/removes 对 abort()
的调用优化大于 -O1
.
奇怪的是,当编译为C++源文件时,即使我们检查return值,警告仍然存在。在 C 中一切都很好。在 C++ 中更喜欢 std::format_to
anyway。所以:
- 我们可以只使用编译器特定的语法来禁用警告。
#include <stdio.h>
#include <stdlib.h>
int main() {
char dst[2];
char src[2] = "a";
// does not warn in C
// warns in C++ with g++ newer than 10.1 with optimization -O2
int ret = snprintf(dst, sizeof(dst), "%s!", src);
if (ret < 0) {
abort();
}
// does not warn in C
// still warns in C++
ret = snprintf(dst, sizeof(dst), "%s!", "a");
if (ret < 0) {
abort();
}
// use compiler specific pragmas to disable the warning
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-truncation"
snprintf(dst, sizeof(dst), "%s!", "a");
#pragma GCC diagnostic pop
// wrapper macro with compiler specific pragmas
// works for any gcc
// works from g++ 10.1
#ifndef __GNUC__
#define snprintf_nowarn snprintf
#else
#define snprintf_nowarn(...) __extension__({ \
_Pragma("GCC diagnostic push"); \
_Pragma("GCC diagnostic ignored \"-Wformat-truncation\""); \
const int _snprintf_nowarn = snprintf(__VA_ARGS__); \
_Pragma("GCC diagnostic pop"); \
_snprintf_nowarn; \
})
#endif
snprintf_nowarn(dst, sizeof(dst), "%s!", "a");
}
此错误仅在调用 length-limited *printf
函数时触发(例如 snprintf
、vsnprintf
)。换句话说,它 而不是 表明您可能正在溢出缓冲区,就像 sprintf 可能发生的那样;它只通知您您没有检查 snprintf
是否正在执行其工作和截断。 (旁注:snprintf
总是 null-terminates,所以这不会导致 non-terminated 字符串。)
知道这一点后,我对使用 -Wno-format-truncation
全局禁用它更加乐观,而不是试图哄骗 gcc
忽略特定实例。
这个页面对我很有用:
https://www.fluentcpp.com/2019/08/30/how-to-disable-a-warning-in-cpp/
您可以通过执行以下操作解决 gcc/clang 编译器的问题:
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-truncation"
snprintf(dst, sizeof(dst), "%s-more", src);
#pragma GCC diagnostic pop
上面的网页也有针对Visual Studio个编译器警告的解决方案。
此外,为目标大小引入一个 volatile
临时变量也是一种解决方法。
char dst[20];
char src[20];
volatile int dst_size = sizeof(dst);
snprintf(dst, dst_size, "%s-more", src);
根据 Martin Sebor 的建议,您也可以使用此 snprintf_trunc
宏;
#define snprintf_trunc(dst, size, ...) \
do { \
volatile size_t n = size; \
snprintf (dst, n, __VA_ARGS__); \
} while (0)