Как безопасно использовать strnlen?

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

Как, например, наличие строки, не заканчивающейся нулем, в качестве входных данных.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main()
{
    void* data = malloc(5);

    size_t len = strnlen((const char*)data, 10);
    printf("len = %zu\n", len);

    return 0;
}

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


person Nick    schedule 11.07.2018    source источник
comment
Как вы определяете длину вашей строки, если она не заканчивается NUL?   -  person Angew is no longer proud of SO    schedule 11.07.2018
comment
Чтобы использовать strnlen(), во втором аргументе необходимо указать верхний предел длины. В вашем случае вызов strnlen() не должен иметь второй аргумент, превышающий 5. Если вы не будете уважать это, вы вызовете неопределенное поведение, и это невозможно обойти.   -  person Peter    schedule 11.07.2018
comment
Вопрос в том, как использовать strnlen и помечен буквой C, а не в том, как избежать strnlen и отказаться от C.   -  person Christian Gibbons    schedule 11.07.2018
comment
C-безопасный подход может быть strnlen или memchr   -  person Jose    schedule 11.07.2018


Ответы (3)


Чтобы безопасно использовать strnlen, вам необходимо

  1. Следите за размером входного буфера самостоятельно (5 в вашем случае) и передайте это в качестве второго параметра, не число больше этого.

  2. Убедитесь, что указатель ввода не NULL.

  3. Убедитесь, что другой поток не записывает в буфер.

Формально вам не нужно инициализировать содержимое буфера, поскольку концептуально функция считывает буфер, как если бы они были char типами.

person Bathsheba    schedule 11.07.2018

Этот код, скорее всего, вызовет неопределенное поведение.

Байты, возвращаемые malloc, имеют неопределенное значение. Если в возвращаемых 5 байтах нет нулевых байтов, то strnlen будет считывать эти байты, поскольку было передано не более 10, а чтение за пределами выделенной памяти вызывает неопределенное поведение.

Однако простое чтение возвращенных байтов не должно быть неопределенным. В то время как неопределенные значения могут содержать представление с ловушкой, strnlen считывает байты, используя char *, а типы символов не имеют представление с ловушкой, поэтому значения просто не указаны, и их чтение безопасно.

Если значение, переданное в strnlen, не превышает размер выделенной памяти, то его использование безопасно.

person dbush    schedule 11.07.2018
comment
Не так быстро. Поведение при чтении неинициализированных байтов char по char не определено. Но на самом деле чтение за пределами буфера является неопределенным. - person Bathsheba; 11.07.2018
comment
@Bathsheba Верно в теории, но очень не верно на практике; каждый компилятор, который я тестировал, принимает J.2. Поведение не определено, если... используется значение [любого типа, независимо от того, есть ли у него представления-ловушки] в то время как оно неопределенно как нормативное, даже при внимательном прочтении фактического текст не идет так далеко. - person zwol; 11.07.2018
comment
В этом случае он не определен, так как выделенный блок имеет размер 5 байт, а strnlen max len равен 10, следовательно, это проблема. В противном случае, если maxlen меньше или равно выделенной длине блока памяти, все должно быть в порядке. - person phoxis; 11.07.2018
comment
@phoxis: это было неправильно до редактирования, я предполагаю, что отрицательные голоса еще не отказались. - person Bathsheba; 11.07.2018
comment
@Bathsheba: Вы правы: вы читаете неинициализированные байты, часть неверна. - person phoxis; 11.07.2018

Поскольку фактическая длина данных равна 5, и у вас, скорее всего, нет '\ 0', он начнет чтение нераспределенной памяти (начиная с данных [5]), что может быть немного неприятно.

person Tutulan Andrei    schedule 11.07.2018
comment
Вы почти ответили на первую часть вопроса (Это поведение не определено?), но не ответили на основной вопрос (Как правильно использовать strnlen()?). Так что, боюсь, это не совсем квалифицируется как ответ. - person Toby Speight; 11.07.2018