Gcc-8 -Wstringop-truncation что такое хорошая практика?

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 - это char* с длиной потенциально больше 32. Я хотел бы скопировать name в this->name и усечь его, если он больше 32. Должен ли size_t быть 31 вместо 32? Я в замешательстве. Необязательно, чтобы this->name заканчивался NUL.

Если name - это "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", хотите ли вы, чтобы this->name содержал "ABCDEFGHIJKLMNOPQRSTUVWXYZabcde" (строку) или {'A','B',...,'e','f'} (последовательность символов без нулевого символа для ее завершения)?

user743382 06.05.2018 12:24

Последовательность символов без завершающего нулевого символа.

JRR 06.05.2018 12:26
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
26
2
20 252
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

Есть очень мало оправданных причин для использования 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() не создает строки с завершающим нулем в стиле C - он делает что-то другое.

John Zwinck 06.05.2018 11:57

Я не уверен, что понимаю, что вы указываете на неоправданное использование strncopy. Я немного редактирую свой вопрос, чтобы объяснить свое намерение. В основном то, что я хочу, чтобы он усек до 32 символов от источника до места назначения.

JRR 06.05.2018 12:20

@JRR, вы хотите, чтобы destination оставался NUL-завершенным? Потому что в этом случае strncpy - неправильная функция.

Matteo Italia 06.05.2018 12:25

@JRR Я отредактировал свой ответ, но вы должны отредактировать свой вопрос, это было непонятно.

benjarobin 06.05.2018 12:32

Какова роль -1 +1? Я предполагаю, что он используется для исключения нулевого байта

JRR 06.05.2018 12:57
Ответ принят как подходящий

Это сообщение пытается предупредить вас, что вы делаете именно то, что делаете. В большинстве случаев это не то, что планировал программист. Если это именно то, что вы намеревались (то есть ваш код будет правильно обрабатывать случай, когда массив символов не будет содержать никаких нулевых символов), отключите предупреждение.

Если вы не хотите или не можете отключить его глобально, вы можете отключить его локально, как указано @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
}

Спасибо. Моя проблема в том, что я выпускаю этот код в месте, где у меня мало возможностей управления параметрами компиляции (в CRAN, месте, где хранятся пакеты языка программирования R). Могу ли я сделать то же самое с memcpy, чтобы пропустить предупреждение без изменения параметров компиляции?

JRR 06.05.2018 12:39

Вы можете использовать #pragma для локального отключения предупреждений.

doron 06.05.2018 13:06

Мой код должен компилироваться без предупреждения, по крайней мере, с gcc 5,6,7,8 и clang 3,4. Это заявление прагмы вызывает предупреждения. Есть ли способ специально настроить gcc 8?

JRR 06.05.2018 13:57

@JRR Если вам нужно, вы можете проверить __GNUC__, но я бы рекомендовал проверять поведение, а не проверять версии, когда это возможно. Вместо этого вы можете игнорировать -Wpragmas, -Wunknown-warning-option и -Wstringop-truncation (в таком порядке). Никаких предупреждений в GCC 4.6 и новее, clang 3.5 и новее.

user743382 06.05.2018 14:28

Это слишком многословно и вызывает предупреждения на других компиляторах. Смотрите мое решение stackoverflow.com/a/56402365/33708

mmx 01.06.2019 00:32

используйте -Wno-stringop-truncation, чтобы отключить это предупреждение, и у вас все еще есть -Wall, как можно увидеть в этот ответ на переполнение стека

m4l490n 12.02.2020 19:45

Если strlen(s) >= 32, этот код вызывает UB, например, при попытке распечатать d.

ivaigult 10.02.2021 17:02

Я обнаружил, что лучший способ подавить предупреждение - заключить в скобки выражение как этот патч gRPC:

(strncpy(req->initial_request.name, lb_service_name,
         GRPC_GRPCLB_SERVICE_NAME_MAX_LENGTH));

Проблема с решением для подавления диагностики #pragma заключается в том, что сама #pragma вызывает предупреждение, когда компилятор не распознает ни прагму, ни конкретное предупреждение; также это слишком многословно.

У меня не работает с strncat. Лично мне такая магия со скобками не нравится.

StSav012 17.07.2019 22:17

Не работает с sprintf, snprinf

avinal 16.03.2021 15:44

Это новое предупреждение GCC делает strncpy() практически непригодным для использования во многих проектах: проверка кода не принимает код, который выдает предупреждения. Но если strncpy() используется только с достаточно короткими строками, чтобы он мог записать завершающий нулевой байт, то обнуление целевого буфера в начале, а затем простой strcpy() приведет к той же задаче.

Собственно, strncpy() - это одна из функций, которую лучше не помещать в библиотеку C. Конечно, есть законные варианты использования. Но разработчики библиотек забыли также включить в стандарт аналоги strncpy() с фиксированным размером строк. Наиболее важные из таких функций, strnlen() и strndup(), были включены в POSIX.1 только в 2008 году, спустя десятилетия после создания 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] = '\0';

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;
}

demo

Как сказано в комментарии, здесь необходим short_string[7] = '\0';. От человека 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")

Смотрите полную статью здесь.

Другие вопросы по теме