Что произойдет, если я запишу менее 12 байтов в 12-байтовый буфер? - PullRequest
0 голосов
/ 17 сентября 2018

Понятно, что выход из буфера ошибок (или переполнение), но что произойдет, если в 12-байтовом буфере используется менее 12 байтов?Возможно ли это, или пустой трейлинг всегда заполняется нулями?Ортогональный вопрос, который может помочь: что содержится в буфере, когда он создается, но еще не используется приложением?

Я посмотрел на несколько программ для домашних животных в Visual Studio, и кажется, что они добавляются с нулями(или нулевые символы), но я не уверен, что это реализация MS, которая может варьироваться в зависимости от языка / компилятора.

Ответы [ 10 ]

0 голосов
/ 17 сентября 2018

Я думаю, что правильный ответ заключается в том, что вы всегда должны следить за тем, сколько написано символов. Как и в случае с низкоуровневыми функциями, такими как чтение и запись, необходимо указать количество прочитанных или записанных символов. Точно так же std :: string отслеживает количество символов в его реализации

0 голосов
/ 17 сентября 2018

Это зависит от спецификатора класса хранилища, вашей реализации и его настроек.Несколько интересных примеров: - переменные неинициализированного стека могут быть установлены на 0xCCCCCCCC - переменные неинициализированной кучи могут быть установлены на 0xCDCDCDCD - неинициализированные статические или глобальные переменные могут быть установлены на 0x00000000 - или это может быть мусором.Делать какие-либо предположения по этому поводу рискованно.

0 голосов
/ 17 сентября 2018

Объявленные объекты статической длительности (те, которые объявлены вне функции или с квалификатором static), у которых нет указанного инициализатора, инициализируются тем значением, которое будет представлено буквальным нулем [т.е. целое число ноль, ноль с плавающей точкой или нулевой указатель, в зависимости от ситуации, или структура или объединение, содержащее такие значения]. Если объявление какого-либо объекта (включая объекты с автоматической продолжительностью) включает инициализатор, части, значения которых указаны этим инициализатором, будут установлены так, как указано, а остаток будет обнулен как со статическими объектами.

Для автоматических объектов без инициализаторов ситуация несколько более неоднозначна. Учитывая что-то вроде:

#include <string.h>

unsigned char static1[5], static2[5];

void test(void)
{
  unsigned char temp[5];
  strcpy(temp, "Hey");
  memcpy(static1, temp, 5);
  memcpy(static2, temp, 5);
}

Стандарт ясно, что test не будет вызывать неопределенное поведение, даже если он копирует части temp, которые не были инициализированы. В тексте Стандарта, по крайней мере, начиная с C11, неясно, гарантируется ли что-либо о значениях static1[4] и static2[4], особенно в том случае, если они могут иметь другие значения. В отчете о дефекте говорится, что Стандарт не был предназначен для того, чтобы запретить компилятору вести себя так, как если бы код был:

unsigned char static1[5]={1,1,1,1,1}, static2[5]={2,2,2,2,2};

void test(void)
{
  unsigned char temp[4];
  strcpy(temp, "Hey");
  memcpy(static1, temp, 4);
  memcpy(static2, temp, 4);
}

, который может оставить static1[4] и static2[4] с разными значениями. В стандарте ничего не говорится о том, должны ли качественные компиляторы, предназначенные для различных целей , вести себя в этой функции. Стандарт также не дает указаний относительно того, как должна быть написана функция, если намерение, если программист требует, чтобы static1[4] и static2[4] содержали одно и то же значение, но не имеет значения, что это за значение.

0 голосов
/ 17 сентября 2018

В общем, для буферов нет ничего необычного в недостаточной заполненности. Часто хорошей практикой является размещение буферов больше, чем нужно. (Попытка всегда вычислить точный размер буфера является частым источником ошибок и часто пустой тратой времени.)

Когда буфер больше необходимого, когда буфер содержит меньше данных, чем его выделенный размер, очевидно, важно отслеживать, сколько там данных . В общем, есть два способа сделать это: (1) с явным счетом, хранящимся в отдельной переменной, или (2) со значением «sentinel», таким как символ \0, который обозначает конец строки в C.

Но тогда возникает вопрос: если не весь буфер используется, что содержат неиспользуемые записи?

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

В основном существует четыре ситуации, в которых вы можете предсказать начальные значения неиспользуемых записей в буфере:

  1. Когда вы выделяете массив (включая массив символов) с продолжительностью static, все неиспользуемые записи инициализируются равными 0.

  2. Когда вы выделяете массив и даете ему явный инициализатор, все неиспользуемые записи инициализируются равными 0.

  3. Когда вы вызываете calloc, выделенная память инициализируется как все биты-0.

  4. При вызове strncpy строка назначения дополняется до размера n с \0 символами.

Во всех других случаях неиспользуемые части буфера непредсказуемы и обычно содержат то, что они делали в прошлый раз (что бы это ни значило). В частности, вы не можете предсказать содержимое неинициализированного массива с автоматической длительностью (то есть того, который является локальным для функции и не объявлен с static), и вы не можете предсказать содержимое памяти, полученной с помощью malloc. (Иногда в этих двух случаях память имеет тенденцию начинаться с нуля, начиная с нуля, но вы определенно не хотите когда-либо зависеть от этого.)

0 голосов
/ 17 сентября 2018

Все предыдущие ответы очень хороши и очень подробны, но ОП кажется новым для программирования на Си.Итак, я подумал, что пример Real World может быть полезен.

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

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

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

Компьютерные буферы работают точно так же.Вот почему вы часто видите переменную bufferSize , чтобы отслеживать объем используемого буфера.Лучшее имя может быть numberOfBytesUsedInMyBuffer , но программисты, как правило, сводят с ума.

0 голосов
/ 17 сентября 2018

Рассмотрим ваш буфер, заполненный нулями:

[00][00][00][00][00][00][00][00][00][00][00][00]

Теперь давайте напишем 10 байтов. Значения с 1:

[01][02][03][04][05][06][07][08][09][10][00][00]

А теперь снова, на этот раз, 4 раза 0xFF:

[FF][FF][FF][FF][05][06][07][08][09][10][00][00]

что произойдет, если в 12-байтовом буфере используется менее 12 байтов? Возможно ли это, или пустой трейлинг всегда заполняется нулями?

Вы пишете столько, сколько хотите, оставшиеся байты остаются без изменений.

Ортогональный вопрос, который может помочь: что содержится в буфере, когда он создается, но еще не используется приложением?

Не выбрано. Ожидайте мусора, оставленного программами (или другими частями вашей программы), которые раньше использовали эту память.

Я просмотрел несколько программ для домашних животных в Visual Studio, и кажется, что они добавляются с нулями (или нулевыми символами), но я не уверен, что это реализация MS, которая может различаться в зависимости от языка / компилятора.

Это именно то, что вы думаете. Кто-то сделал это для вас в этот раз, но нет никаких гарантий, что это случится снова. Это может быть флаг компилятора, который присоединяет код очистки. Некоторые версии MSVC заполняли новую память 0xCD при запуске в режиме отладки, но не в выпуске. Это также может быть функция безопасности системы, которая стирает память перед передачей ее процессу (поэтому вы не можете шпионить за другими приложениями). Не забывайте использовать memset для инициализации буфера там, где это важно. В конце концов, необходимо использовать определенный флаг компилятора в readme, если вы хотите, чтобы свежий буфер содержал определенное значение.

Но чистка на самом деле не нужна. Вы берете 12-байтовый буфер. Вы заполняете его 7 байтами. Затем вы передаете его куда-нибудь - и говорите: «Вот вам 7 байтов». Размер буфера не имеет значения при чтении из него. Вы ожидаете, что другие функции будут читать столько, сколько вы написали, а не столько, сколько возможно. Фактически, в C обычно невозможно определить, как долго находится буфер.

И примечание:

Понятно, что выход из буфера ошибок (или создание переполнения)

Нет, вот в чем проблема. Вот почему это огромная проблема безопасности: ошибки нет, и программа пытается продолжить работу, поэтому иногда она запускает вредоносный контент, который никогда не предназначался. Поэтому нам пришлось добавить в ОС кучу механизмов, таких как ASLR, которые увеличат вероятность сбоя программы и уменьшат вероятность ее продолжения с поврежденной памятью. Так что, никогда не полагайтесь на охранников запоздалой мысли и следите за своими буферными границами.

0 голосов
/ 17 сентября 2018

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

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

Любой пробел после этого в буфере остается нетронутым.Если ранее там были данные, они все еще там.Это то, что мы называем мусором.

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

0 голосов
/ 17 сентября 2018

Запись части буфера не повлияет на неписанную часть буфера; он будет содержать все, что было там заранее (что, естественно, целиком и полностью зависит от того, как вы получили буфер).

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

Инициализация памяти кучи осуществляется по усмотрению диспетчера памяти, но в целом она также не будет инициализирована.

0 голосов
/ 17 сентября 2018

Возьмите следующий пример (в пределах блока кода, не глобального):

char data[12];
memcpy(data, "Selbie", 6);

Или даже этот пример:

char* data = new char[12];
memcpy(data, "Selbie", 6);

В обоих вышеупомянутых случаях первый6 байтов data равны S, e, l, b, i и e.Остальные 6 байтов data считаются «неопределенными» (может быть чем угодно).

Возможно ли это, или пустой трейлинг всегда заполняется нулями?

Не гарантируется вообще.Единственный известный мне распределитель, который гарантирует заполнение нулевым байтом, это calloc .Пример:

char* data = calloc(12,1);  // will allocate an array of 12 bytes and zero-init each byte
memcpy(data, "Selbie");

что содержится в буфере, когда он создается, но еще не используется приложением?

Технически, согласно самым последним стандартам C ++, байты, доставленные распределителем, технически считаются «неопределенными».Вы должны предположить, что это мусорные данные (что угодно).Не делайте предположений относительно содержимого.

Отладочные сборки с помощью Visual Studio часто инициализируют буферы со значениями 0xcc или 0xcd, но это не относится к выпускным сборкам.Тем не менее, существуют флаги компилятора и методы выделения памяти для Windows и Visual Studio, где можно гарантировать выделение памяти с нулевым инициализацией, но оно не переносимо.

0 голосов
/ 17 сентября 2018

C ++ имеет классы хранения, включая глобальные, автоматические и статические.Инициализация зависит от того, как объявлена ​​переменная.

char global[12];  // all 0
static char s_global[12]; // all 0

void foo()
{
   static char s_local[12]; // all 0
   char local[12]; // automatic storage variables are uninitialized, accessing before initialization is undefined behavior 
}

Некоторые интересные детали здесь .

...