gcc-8 -Wstringop-truncation 什么是好的做法?
gcc-8 -Wstringop-truncation what is the good practice?
GCC 8 添加了一个 -Wstringop-truncation
警告。来自 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82944 :
The -Wstringop-truncation warning added in GCC 8.0 via r254630 for bug 81117 is specifically intended to highlight likely unintended uses of the strncpy function that truncate the terminating NUL charcter from the source string. An example of such a misuse given in the request is the following:
char buf[2];
void test (const char* str)
{
strncpy (buf, str, strlen (str));
}
我收到与此代码相同的警告。
strncpy(this->name, name, 32);
warning: 'char* strncpy(char*, const char*, size_t)' specified bound 32 equals destination size [-Wstringop-truncation`]
考虑到 this->name
是 char name[32]
而 name
是长度可能大于 32 的 char*
。我想将 name
复制到this->name
如果大于 32 则将其截断。size_t
应该是 31 而不是 32?我很困惑。 this->name
不是必须以 NUL 结尾的。
很少有理由使用 strncpy
。这是一个相当危险的功能。如果源字符串长度(不含空字符)等于目标缓冲区大小,则 strncpy
不会在目标缓冲区末尾添加空字符。因此目标缓冲区不会以空值终止。
我们应该在Linux上写这样的代码:
lenSrc = strnlen(pSrc, destSize)
if (lenSrc < destSize)
memcpy(pDest, pSrc, lenSrc + 1);
else {
/* Handle error... */
}
在你的情况下,如果你想在复制时截断源,但仍然想要一个空终止的目标缓冲区,那么你可以编写这种代码:
destSize = 32
sizeCp = strnlen(pSrc, destSize - 1);
memcpy(pDest, pSrc, sizeCp);
pDest[sizeCp] = '[=11=]';
编辑:哦...如果这不是强制以 NULL 终止,strncpy
是正确的函数。是的,你需要用 32 而不是 31 来调用它。
我认为您需要通过禁用它来忽略此警告...老实说,我对此没有好的答案...
Edit2:为了模仿strncpy
函数,你可以写这样的代码:
destSize = 32
sizeCp = strnlen(pSrc, destSize - 1);
memcpy(pDest, pSrc, sizeCp + 1);
此消息试图警告您,您正在做的正是您正在做的事情。很多时候,这不是程序员想要的。如果这是您想要的(意思是,您的代码将正确处理字符数组最终不包含任何空字符的情况),请关闭警告。
如果你不想或不能全局关闭它,你可以像@doron 指出的那样在本地关闭它:
#include <string.h>
char d[32];
void f(const char *s) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstringop-truncation"
strncpy(d, s, 32);
#pragma GCC diagnostic pop
}
我发现抑制警告的最佳方法是将表达式放在括号中 like this gRPC patch:
(strncpy(req->initial_request.name, lb_service_name,
GRPC_GRPCLB_SERVICE_NAME_MAX_LENGTH));
#pragma
诊断抑制解决方案的问题在于,当编译器无法识别 pragma 或特定警告时,#pragma 本身会导致警告;也太冗长了。
这个新的 GCC 警告使 strncpy()
在许多项目中几乎无法使用:代码审查不会接受产生警告的代码。但是,如果 strncpy()
仅用于足够短的字符串,以便它可以写入终止零字节,然后在开始时将目标缓冲区清零,然后使用普通 strcpy()
将实现相同的工作。
其实strncpy()
是其中的一个函数,最好不要放到C库里。当然,它有合法的用例。但是库设计者也忘记将 strncpy()
的固定大小字符串感知对应项放入标准中。最重要的此类函数 strnlen()
和 strndup()
仅在 2008 年包含在 POSIX.1 中,距 strncpy()
创建几十年后!并且仍然没有函数将 strncpy()
生成的固定长度字符串复制到具有正确 C 语义的预分配缓冲区中,即始终写入 0 终止字节。一个这样的函数可以是:
// Copy string "in" with at most "insz" chars to buffer "out", which
// is "outsz" bytes long. The output is always 0-terminated. Unlike
// strncpy(), strncpy_t() does not zero fill remaining space in the
// output buffer:
char* strncpy_t(char* out, size_t outsz, const char* in, size_t insz){
assert(outsz > 0);
while(--outsz > 0 && insz > 0 && *in) { *out++ = *in++; insz--; }
*out = 0;
return out;
}
我建议为 strncpy_t()
使用两个长度输入,以避免混淆:如果只有一个 size
参数,则不清楚,如果它是输出缓冲区的大小或输入字符串的最大长度(通常少一)。
它说的是我们只能使用 len - 1 个字符,因为最后一个字符应该是 '\0',所以使用似乎清除了警告我们只能复制 len - 1 ...
举个例子:
strncpy(this->name, name, 31);
或
#include <string.h>
char d[32];
void f(const char *s) {
strncpy(d, s, 31);
}
d[31] = '[=11=]';
TL;DR: 处理截断情况,警告将消失。
这个警告碰巧对我很有用,因为它发现了我的代码中的一个问题。考虑这个清单:
#include <string.h>
#include <stdio.h>
int main() {
const char long_string[] = "It is a very long string";
char short_string[8];
strncpy(short_string, long_string, sizeof(short_string));
/* This line is extremely important, it handles string truncation */
short_string[7] = '[=10=]';
printf("short_string = \"%s\"\n", short_string);
return 0;
}
正如评论所说,short_string[7] = '[=11=]';
在这里是必要的。来自 strncpy
人:
Warning: If there is no null byte among the first n bytes of src, the string placed in dest will not be null-terminated.
如果我们删除此行,它会调用 UB。比如我,程序开始打印:
short_string = "It is a It is a very long string"
基本上,GCC 希望您修复 UB。我在我的代码中添加了这样的处理,警告消失了。
我在寻找这个问题的近乎完美的解决方案时发现了这一点。由于此处的大多数答案都描述了在不抑制警告的情况下如何处理的可能性和方法。接受的答案建议使用以下包装器,这会导致另一组警告并且令人沮丧且不可取。
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstringop-truncation"
...
#pragma GCC diagnostic pop
相反,我找到了这个可行的解决方案,不能说是否有任何陷阱,但它工作得很好。
_Pragma("GCC diagnostic push")
_Pragma("GCC diagnostic ignored \"-Wstringop-truncation\"")
strncpy(d, s, 32);
_Pragma("GCC diagnostic pop")
查看全文 here。
其他人的回应让我只写了一个简单版本的 strncpy。
#include<string.h>
char* mystrncpy(char* dest, const char*src, size_t n) {
memset(dest, 0, n);
memcpy(dest, src, strnlen(src, n-1));
return dest;
}
它避免了警告并保证 dest 是空终止的。我正在使用 g++ 编译器并希望避免 pragma 条目。
GCC 8 添加了一个 -Wstringop-truncation
警告。来自 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82944 :
The -Wstringop-truncation warning added in GCC 8.0 via r254630 for bug 81117 is specifically intended to highlight likely unintended uses of the strncpy function that truncate the terminating NUL charcter from the source string. An example of such a misuse given in the request is the following:
char buf[2];
void test (const char* str)
{
strncpy (buf, str, strlen (str));
}
我收到与此代码相同的警告。
strncpy(this->name, name, 32);
warning: 'char* strncpy(char*, const char*, size_t)' specified bound 32 equals destination size [-Wstringop-truncation`]
考虑到 this->name
是 char name[32]
而 name
是长度可能大于 32 的 char*
。我想将 name
复制到this->name
如果大于 32 则将其截断。size_t
应该是 31 而不是 32?我很困惑。 this->name
不是必须以 NUL 结尾的。
很少有理由使用 strncpy
。这是一个相当危险的功能。如果源字符串长度(不含空字符)等于目标缓冲区大小,则 strncpy
不会在目标缓冲区末尾添加空字符。因此目标缓冲区不会以空值终止。
我们应该在Linux上写这样的代码:
lenSrc = strnlen(pSrc, destSize)
if (lenSrc < destSize)
memcpy(pDest, pSrc, lenSrc + 1);
else {
/* Handle error... */
}
在你的情况下,如果你想在复制时截断源,但仍然想要一个空终止的目标缓冲区,那么你可以编写这种代码:
destSize = 32
sizeCp = strnlen(pSrc, destSize - 1);
memcpy(pDest, pSrc, sizeCp);
pDest[sizeCp] = '[=11=]';
编辑:哦...如果这不是强制以 NULL 终止,strncpy
是正确的函数。是的,你需要用 32 而不是 31 来调用它。
我认为您需要通过禁用它来忽略此警告...老实说,我对此没有好的答案...
Edit2:为了模仿strncpy
函数,你可以写这样的代码:
destSize = 32
sizeCp = strnlen(pSrc, destSize - 1);
memcpy(pDest, pSrc, sizeCp + 1);
此消息试图警告您,您正在做的正是您正在做的事情。很多时候,这不是程序员想要的。如果这是您想要的(意思是,您的代码将正确处理字符数组最终不包含任何空字符的情况),请关闭警告。
如果你不想或不能全局关闭它,你可以像@doron 指出的那样在本地关闭它:
#include <string.h>
char d[32];
void f(const char *s) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstringop-truncation"
strncpy(d, s, 32);
#pragma GCC diagnostic pop
}
我发现抑制警告的最佳方法是将表达式放在括号中 like this gRPC patch:
(strncpy(req->initial_request.name, lb_service_name,
GRPC_GRPCLB_SERVICE_NAME_MAX_LENGTH));
#pragma
诊断抑制解决方案的问题在于,当编译器无法识别 pragma 或特定警告时,#pragma 本身会导致警告;也太冗长了。
这个新的 GCC 警告使 strncpy()
在许多项目中几乎无法使用:代码审查不会接受产生警告的代码。但是,如果 strncpy()
仅用于足够短的字符串,以便它可以写入终止零字节,然后在开始时将目标缓冲区清零,然后使用普通 strcpy()
将实现相同的工作。
其实strncpy()
是其中的一个函数,最好不要放到C库里。当然,它有合法的用例。但是库设计者也忘记将 strncpy()
的固定大小字符串感知对应项放入标准中。最重要的此类函数 strnlen()
和 strndup()
仅在 2008 年包含在 POSIX.1 中,距 strncpy()
创建几十年后!并且仍然没有函数将 strncpy()
生成的固定长度字符串复制到具有正确 C 语义的预分配缓冲区中,即始终写入 0 终止字节。一个这样的函数可以是:
// Copy string "in" with at most "insz" chars to buffer "out", which
// is "outsz" bytes long. The output is always 0-terminated. Unlike
// strncpy(), strncpy_t() does not zero fill remaining space in the
// output buffer:
char* strncpy_t(char* out, size_t outsz, const char* in, size_t insz){
assert(outsz > 0);
while(--outsz > 0 && insz > 0 && *in) { *out++ = *in++; insz--; }
*out = 0;
return out;
}
我建议为 strncpy_t()
使用两个长度输入,以避免混淆:如果只有一个 size
参数,则不清楚,如果它是输出缓冲区的大小或输入字符串的最大长度(通常少一)。
它说的是我们只能使用 len - 1 个字符,因为最后一个字符应该是 '\0',所以使用似乎清除了警告我们只能复制 len - 1 ...
举个例子:
strncpy(this->name, name, 31);
或
#include <string.h>
char d[32];
void f(const char *s) {
strncpy(d, s, 31);
}
d[31] = '[=11=]';
TL;DR: 处理截断情况,警告将消失。
这个警告碰巧对我很有用,因为它发现了我的代码中的一个问题。考虑这个清单:
#include <string.h>
#include <stdio.h>
int main() {
const char long_string[] = "It is a very long string";
char short_string[8];
strncpy(short_string, long_string, sizeof(short_string));
/* This line is extremely important, it handles string truncation */
short_string[7] = '[=10=]';
printf("short_string = \"%s\"\n", short_string);
return 0;
}
正如评论所说,short_string[7] = '[=11=]';
在这里是必要的。来自 strncpy
人:
Warning: If there is no null byte among the first n bytes of src, the string placed in dest will not be null-terminated.
如果我们删除此行,它会调用 UB。比如我,程序开始打印:
short_string = "It is a It is a very long string"
基本上,GCC 希望您修复 UB。我在我的代码中添加了这样的处理,警告消失了。
我在寻找这个问题的近乎完美的解决方案时发现了这一点。由于此处的大多数答案都描述了在不抑制警告的情况下如何处理的可能性和方法。接受的答案建议使用以下包装器,这会导致另一组警告并且令人沮丧且不可取。
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstringop-truncation"
...
#pragma GCC diagnostic pop
相反,我找到了这个可行的解决方案,不能说是否有任何陷阱,但它工作得很好。
_Pragma("GCC diagnostic push")
_Pragma("GCC diagnostic ignored \"-Wstringop-truncation\"")
strncpy(d, s, 32);
_Pragma("GCC diagnostic pop")
查看全文 here。
其他人的回应让我只写了一个简单版本的 strncpy。
#include<string.h>
char* mystrncpy(char* dest, const char*src, size_t n) {
memset(dest, 0, n);
memcpy(dest, src, strnlen(src, n-1));
return dest;
}
它避免了警告并保证 dest 是空终止的。我正在使用 g++ 编译器并希望避免 pragma 条目。