Что произойдет, если я напишу меньше 12 байтов в 12-байтовый буфер?

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

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

Ответы

Ответ 1

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

[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 байтов? Возможно ли, или пустой пудинг всегда заполняется 0s?

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

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

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

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

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

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

И сторона примечания:

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

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

Ответ 2

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

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 считаются "неуказанными" (может быть что угодно).

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

Не гарантируется вообще. Единственный распределитель, который я знаю об этом, гарантирует заполнение нулевого байта - 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, где вы можете гарантировать выделение нулевой памяти, но это не переносимо.

Ответ 3

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 
}

Некоторые интересные подробности здесь.

Ответ 4

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

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

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

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

Ответ 5

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

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

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

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

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

Ответ 6

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

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

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

Ответ 7

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

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

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

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

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

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

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

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

  4. Когда вы вызываете strncpy, строка назначения добавляется к размеру n с символами \0.

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

Ответ 8

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

Ответ 9

Объявленные объекты статической продолжительности (те, которые объявлены вне функции или со 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 не будет вызывать Undefined Behavior, даже если он копирует части 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] и то же значение, но все равно, какое это значение.

Ответ 10

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