Что происходит, когда strnlen () используется с большей максимальной длиной, чем размер буфера? - PullRequest
0 голосов
/ 06 июня 2018

Я написал следующий код, чтобы лучше понять, как ведет себя strnlen:

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

int main(int argc, char **argv)
{
    char bufferOnStack[10]={'a','b','c','d','e','f','g','h','i','j'};
    char *bufferOnHeap = (char *) malloc(10);

    bufferOnHeap[ 0]='a';
    bufferOnHeap[ 1]='b';
    bufferOnHeap[ 2]='c';
    bufferOnHeap[ 3]='d';
    bufferOnHeap[ 4]='e';
    bufferOnHeap[ 5]='f';
    bufferOnHeap[ 6]='g';
    bufferOnHeap[ 7]='h';
    bufferOnHeap[ 8]='i';
    bufferOnHeap[ 9]='j';

    int lengthOnStack = strnlen(bufferOnStack,39);
    int lengthOnHeap  = strnlen(bufferOnHeap, 39);

    printf("lengthOnStack = %d\n",lengthOnStack);
    printf("lengthOnHeap  = %d\n",lengthOnHeap);

    return 0;
}

Обратите внимание на преднамеренное отсутствие нулевого завершения в обоих буферах.Согласно документации, кажется, что длины должны быть 39:

ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ Функция strnlen () возвращает strlen (s), если это меньше, чем maxlen, или maxlen, если нетокончание нуля ('\ 0') среди первых максленовых символов, на которые указывает s.

Вот моя строка компиляции:

$ gcc ./main_08.c -o main

И вывод:

$ ./main
lengthOnStack = 10
lengthOnHeap  = 10

Что здесь происходит?Спасибо!

Ответы [ 4 ]

0 голосов
/ 06 июня 2018

Ваш вопрос примерно эквивалентен следующему:

Я знаю, что охранная сигнализация должна предотвратить ограбление вашего дома.Этим утром, когда я вышел из дома, я выключил охранную сигнализацию.Когда-то днем, когда меня не было, ворвался грабитель и украл мои вещи.Как это произошло?

Или вот что:

Я знаю, что вы можете использовать круиз-контроль на своей машине, чтобы помочь вам избежать получения штрафов за превышение скорости.Вчера я ехал по дороге, где ограничение скорости составляло 65. Я установил круиз-контроль на 95. Полицейский остановил меня, и я получил штраф за превышение скорости.Как это случилось?

На самом деле, это не совсем правильно.Вот более надуманная аналогия:

Я живу в доме с 10-метровой дорогой на улицу.Я научил свою собаку приносить мою газету.Однажды я позаботился о том, чтобы на дороге не было газет.Я положил свою собаку на 39-метровый поводок и сказал ему принести газетную башню.Я ожидал, что он пойдет до конца поводка, 39 ярдов.Но вместо этого он прошел всего 10 ярдов и остановился.Как это случилось?

И, конечно, есть много ответов.Возможно, когда ваша собака добралась до конца вашей дороги без газет, она сразу же нашла чужую газету в канаве.Или, может быть, когда поводок не смог остановить его в конце проезжей части, и он продолжил движение по улице, его сбила машина.

Смысл повесить собаку на поводке - ограничить егов безопасный район - в этом случае ваша собственность, которую вы контролируете.Если вы положите его на такой длинный поводок, что он сможет уйти на улицу или в лес, вы как бы побеждаете цель контролировать его, ставя его на поводок.


Точно так же весь смысл strnlen в том, чтобы вести себя изящно, если в указанном вами буфере нет нулевого символа для strnlen, который нужно найти.

Проблема со строками, не оканчивающимися нулем, заключается втакие функции, как strlen (которые слепо ищут нулевые терминаторы) уходят с конца и слепо копаются в неопределенной памяти, отчаянно пытаясь найти терминатор.Например, если вы скажете

char non_null_terminated_string[3] = "abc";
int len = strlen(non_null_terminated_string);

, поведение не определено, потому что strlen плывет с конца.Один из способов исправить это - использовать strnlen:

char non_null_terminated_string[3] = "abc";
int len = strnlen(non_null_terminated_string, 3);

Но если вы передадите большее число на strnlen, это победит всю цель.Вы снова задаетесь вопросом, что произойдет, когда strnlen уйдет с конца, и нет никакого способа ответить на это.

0 голосов
/ 06 июня 2018

Прежде всего, strnlen() не определяется стандартом C;это стандартная функция POSIX.

Как говорится, внимательно прочитайте документацию

Функция strnlen() возвращает количество байтов в строке, на которую указываетна s, исключая завершающий нулевой байт ('\ 0'), но не более maxlen.При этом strnlen() смотрит только на первые maxlen байты в s и никогда не выходит за пределы s+maxlen.

Так что при вызове функции вам необходимо убедиться, чтодля значения, которое вы указываете для maxlen, значение idexing массива действительно для [maxlen -1] для предоставленной строки, т. е. string содержит как минимум maxlen элементов в нем.

В противном случае, при доступе к строке вы попадете в область памяти, которая не выделена вам (массив вне привязанного доступа), тем самым вызывая неопределенное поведение .

Помните, эта функция предназначена для вычисления длины массива, связанной с верхним значением (maxlen).Это означает, что поставляемые массивы, по крайней мере, равны или больше, чем границы, а не наоборот.


[Сноска]:

По определению строка заканчивается нулем.

Цитата C11, глава §7.1.1, Определения терминов

Строка - это непрерывная последовательность символов, оканчивающаяся первым нулевым символом и включающая его.[...]

0 голосов
/ 06 июня 2018

Что происходит, когда ... " Неопределенное поведение (UB) " ?

«Когда компилятор встречает [заданную неопределенную конструкцию], он может заставить демонов вылететь из вашего носа»

На самом деле ваш заголовокне UB , поскольку вызов strnlen("hi", 5) совершенно законен, но специфика вашего вопроса показывает, что это действительно UB ...

Обе strlen и strnlen ожидаютстрока, то есть последовательность char с нулевым символом в конце.Для вашей функции char массив с ненулевым завершением равен UB .

В вашем случае происходит то, что функция читает первые 10 char s, не находит '\0', и поскольку он не вышел за пределы , он продолжает читать дальше, и тем самым вызывает UB (чтение нераспределенной памяти).Возможно, ваш компилятор позволил завершить ваш массив '\0', возможно, что '\0' был там раньше ... возможности ограничены только разработчиками компилятора.

0 голосов
/ 06 июня 2018

Во-первых, не разыгрывайте malloc .

Во-вторых, вы читаете после конца своих массивов.Память за пределами вашего массива не определена, и поэтому нет гарантии, что она не равна нулю;в данном случае это!

В общем, этот тип поведения неаккуратен - см. этот ответ , чтобы получить хорошее резюме потенциальных последствий

...