gcc-8 -Wringring-усечение, что является хорошей практикой?
GCC 8 добавил -Wstringop-truncation
предупреждение. С https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82944:
Предупреждение -Wstringop-truncation, добавленное в GCC 8.0 через r254630 для ошибки 81117, специально предназначено для выделения вероятного непреднамеренного использования функции strncpy, которая усекает завершающий символ NUL из исходной строки. Примером такого неправильного использования, указанного в запросе, является следующий:
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
это char*
с длиной потенциально больше 32. Я хотел бы скопировать name
в this->name
и обрезать его, если оно больше 32. Должно size_t
быть 31 вместо 32? Я не совсем понимаю. Это не обязательно для this->name
быть обнуляемым.
9 ответов
Это сообщение пытается предупредить вас, что вы делаете именно то, что делаете. В большинстве случаев это не то, что задумал программист. Если это именно то, что вы намеревались (имеется в виду, что ваш код будет правильно обрабатывать случай, когда массив символов не будет содержать никаких нулевых символов), отключите предупреждение.
Если вы не хотите или не можете отключить его глобально, вы можете отключить его локально, как указано @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
}
Это новое предупреждение GCC отображает strncpy()
в основном непригоден для использования во многих проектах: проверка кода не принимает код, который выдает предупреждения. Но еслиstrncpy()
используется только с достаточно короткими строками, чтобы можно было записать завершающий нулевой байт, затем обнулить целевой буфер в начале, а затем просто strcpy()
достигнет той же работы.
Фактически, strncpy()
- одна из функций, которую лучше не помещать в библиотеку C. Конечно, для этого есть законные варианты использования. Но разработчики библиотек забыли поместить вstrncpy()
в стандарт тоже. Самые важные такие функции,strnlen()
а также strndup()
, были включены в POSIX.1 только в 2008 г., спустя десятилетия после strncpy()
был создан! А функции, копирующейstrncpy()
сгенерированная строка фиксированной длины в предварительно выделенный буфер с правильной семантикой C, т.е. всегда записывает нулевой байт завершения. Одной из таких функций может быть:
// 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
аргумент, было бы неясно, является ли это размер выходного буфера или максимальная длина входной строки (которая обычно на единицу меньше).
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] = '\0';
printf("short_string = \"%s\"\n", short_string);
return 0;
}
Как говорится в комментарии
short_string[7] = '\0';
здесь нужно. От
strncpy
человек:
Предупреждение: Если среди первых n байтов src нет нулевого байта, строка, помещенная в dest, не будет завершаться нулевым символом в конце.
Если мы удалим эту строку, она вызовет UB. Например, у меня программа начинает печатать:
short_string = "Это очень длинная строка"
По сути, GCC хочет, чтобы вы исправили UB. Я добавил такую обработку в свой код, и предупреждение исчезло.
Есть очень мало обоснованных случаев использования 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] = '\0';
Редактировать: О... Если это не обязательно для NULL прекращается, strncpy
это правильная функция для использования. И да, вам нужно называть его с 32, а не с 31. Я думаю, что вы должны игнорировать это предупреждение, отключив его... Честно говоря, у меня нет хорошего ответа на это...
Edit2: для того, чтобы имитировать strncpy
функция, вы могли бы написать этот код:
destSize = 32
sizeCp = strnlen(pSrc, destSize - 1);
memcpy(pDest, pSrc, sizeCp + 1);
Ответы других побудили меня написать простую версию 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++ и хотел избежать записей прагмы.
Я обнаружил, что лучший способ подавить предупреждение - поместить выражение в круглые скобки, как этот патч gRPC:
(strncpy(req->initial_request.name, lb_service_name,
GRPC_GRPCLB_SERVICE_NAME_MAX_LENGTH));
Проблема с #pragma
Решение для подавления диагностики состоит в том, что сама #pragma вызовет предупреждение, когда компилятор не распознает ни прагму, ни конкретное предупреждение; и это слишком многословно.
Я нашел это, когда искал почти идеальное решение этой проблемы. Поскольку большинство ответов здесь описывают возможность и способы обработки без подавления предупреждения. В принятом ответе предлагается использовать следующую оболочку, которая приводит к другому набору предупреждений, разочаровывает и нежелательна.
#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")
Смотрите полную статью здесь .
В нем говорится, что мы можем использовать только символы 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] = '\0';
Просто отключите предупреждение прагмы, пока вы это делаете. Тогда сработает локальное отключение предупреждения.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpragmas"
#pragma GCC diagnostic ignored "-Wstringop-truncation"
...
#pragma GCC diagnostic pop