Переполнение строк C

#c #arrays #string #overflow #bounds

#c #массивы #строка #переполнение #границы

Вопрос:

Вот измененная версия кода со страницы 586 «Начало работы с C — от управляющих структур до объектов, 6e».:

 #include <iostream>
using namespace std;

int countChars(char *, char);

int main()
{
    const int SIZE = 5;
    char userString[SIZE];
    char letter;

    cout << "Enter a string: ";
    cin.getline(userString, 10);

    letter = '';
    cout << "a appears ";
    cout << countChars(userString, 'a') << " times.n";

    cin >> letter;
    return 0;
}

int countChars(char *strPtr, char ch)
{
    int times = 0;
    while (*strPtr != '')
    {
        if (*strPtr == ch)
            times  ;
        strPtr  ;
    }
    return times;
}
  

Теперь запустите программу и введите «aaaabba».

Теперь я специально попытался ввести здесь неправильную запись в память. Например. я заявляю, что размер массива символов равен 5, но при появлении запроса введите более 4 (5 минус длина 0) символов.

Предполагая, что система выделила память для «letter» сразу после «UserString», то из этого следует, что когда я что-то записываю в «letter», оно должно перезаписывать соответствующее местоположение в «расширенной» строке пользователя.

Итак, память должна выглядеть следующим образом: [a][a][a][a][a][][b][a][].

Затем, когда я запускаю функцию countChars, она, согласно книге, должна останавливаться на символе ‘ 0’, который находится сразу после первых четырех букв «а».

По этой логике он должен выводить, что в строке есть 4 буквы A.

На самом деле программа говорит, что существует 5 a.

Где ошибка в моих рассуждениях?

РЕДАКТИРОВАТЬ # 1: Это НЕ код из книги. Это МОДИФИЦИРОВАННЫЙ код.

ПРАВКА # 2: я специально изменил код, чтобы ввести переполнение строк. Я сделал это специально, потому что хочу посмотреть, действительно ли память работает так, как я думаю. Итак, что я хочу услышать, так это убедительное объяснение того, почему ошибка не работает так, как я ожидаю.

ПРАВКА # 3: Компилятор жалуется на поврежденный стек, но я нажимаю «Продолжить», потому что хочу посмотреть, что произойдет.

Спасибо.

Комментарии:

1. Марлон и Керрек С.Б., спасибо за совет. Что касается выброса книги, я не думаю, что это очень хорошая книга, и я настоятельно рекомендую ее. Поскольку в прошлом я изучал компьютерную архитектуру и знаю кое-что о том, как работает память в целом, я хочу проверить, применимо ли то, что я знаю, к C .

2. @akanevsky: Если вы новичок в C и имеете дело с указателями символов, вы делаете это неправильно. Если вы почерпнули идею из книги, пришло время поискать другую книгу. Я не говорю, что указатели на символы не имеют своего места; это просто неправильный способ думать о C в учебном пособии или вводном контексте.

3. Как я уже объяснял, я специально делаю это неправильно. Я хотел протестировать преамбулу к коду, в которой говорится: «Если строковый адрес строки передается в переменную параметра указателя, можно предположить, что все символы, начиная с этого адреса и заканчивая байтом, который содержит нулевой ограничитель, являются частью строки». Итак, в основном, то, что я хотел сделать, это создать ситуацию, когда строка будет записана за ее пределами, а затем завершить ее, как я сделал выше, и проверить, работает ли вычисление по-прежнему. У меня только что появилась другая идея — удалить конечный разделитель и посмотреть, что произойдет.

Ответ №1:

Даже если вы выделили место только для 5 символов, проверка не выполняется, и в результате ваша программа нагло перезаписывает все, что было по адресу после вашего массива. В вашем конкретном случае вам (не) повезло, и вы не увидели сбоя — но на самом деле это неопределенное поведение. Единственный нулевой ограничитель находится в конце строки, которую вы читаете, а не на пятой позиции, поэтому вы видите все a буквы. Это неправильный способ делать что-либо…

Ответ №2:

В C или C нет правила, согласно которому локальные переменные должны распределяться в каком-либо определенном порядке. Или даже вообще находиться в стеке. Ваш char может существовать только в регистре процессора. Это может предшествовать массиву. Размер массива может быть увеличен до ближайших 16 байт, чтобы упростить операции SSE.

Комментарии:

1. Правильно, компилятор может выполнять оптимизацию и распределять переменные в порядке, отличном от записанного. Я забыл. Спасибо.

Ответ №3:

Компилятор не имеет абсолютно никаких обязательств по выделению letter после userString . Если вы работаете в режиме отладки, он будет размещать отладочную информацию в середине. Если вы работаете в режиме выпуска, это, вероятно, в регистре, и в стеке может быть что угодно.

Комментарии:

1. Правильно, компилятор может выполнять оптимизацию и распределять переменные в порядке, отличном от записанного. Я забыл. Спасибо.

Ответ №4:

Если вам интересно, как ваши переменные стека расположены относительно друг друга, почему бы не добавить

 cout << ((int)userString) << endl << ((int)amp;letter) << endl;
  

?

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

(Предостережение: Zan Lynx совершенно справедливо упоминает, что letter разрешено просто находиться в регистре процессора, а не в стеке вообще. Однако приведенная выше строка включает amp;letter , что означает, что компилятор должен поместить letter в стек, поскольку регистры не имеют адресов памяти. Таким образом, приведенная выше строка может фактически повлиять на поведение вашей программы, предотвращая оптимизацию компилятора. Вы можете внезапно обнаружить, что есть только четыре а!)

Комментарии:

1. На самом деле, теперь я получаю сбой, если ввожу слишком много символов! Если я этого не сделаю, вывод показывает, что память, выделенная для letter, на самом деле находится перед UserString. Это также показывает, что вы и вышеприведенные плакаты правы, компилятор сначала даже не поместил переменную в стек. Это именно то, что мне нужно было увидеть. Большое вам спасибо.

2. void* Приведение было бы гораздо более подходящим в этой ситуации.