Контакт с символьными буферами

Как программист на C++ мне иногда нужно соглашение с буферами памяти с помощью методов от C. Например:

char buffer[512];
sprintf(buffer, "Hello %s!", userName.c_str());

Или в Windows:

TCHAR buffer[MAX_PATH+1]; // edit: +1 added
::GetCurrentDirectory(sizeof(buffer)/sizeof(TCHAR), &buffer[0]);

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

  • Передает буфер как &buffer[0] лучший стиль программирования, чем передача buffer? (Я предпочитаю &buffer[0].)
  • Существует ли максимальный размер, который считают безопасным для выделенных буферов стека?
    • Обновление: Я имею в виду, например, самое высокое значение, которое можно считать безопасным для межплатформенных настольных приложений на Mac, Windows, рабочие столы Linux (не мобильный!).
  • Статический буфер (static char buffer[N];) быстрее? Есть ли какие-либо другие аргументы за или против него?
  • При использовании статических буферов можно использовать тип возврата const char *. Это - (обычно) польза или плохая идея? (Я действительно понимаю, что вызывающая сторона должна будет сделать его собственную копию, чтобы избежать, чтобы следующий вызов изменил бы предыдущее возвращаемое значение.)
  • Что относительно использования static char * buffer = new char[N]; , никогда не удаляя буфер и снова используя его на каждом вызове.
  • Я понимаю, что выделение "кучи" должно использоваться при (1) контакте с большими буферами или (2) максимальный размер буфера неизвестен во время компиляции. Есть ли какие-либо другие факторы, которые играют в решении выделения стека/"кучи"?
  • Если Вы предпочитаете sprintf_s, memcpy_s... варианты? (Visual Studio пыталась убедить меня в этом в течение долгого времени, но я хочу второе мнение :p)
8
задан StackedCrooked 24 June 2010 в 19:43
поделиться

9 ответов

Я предполагаю, что ваш интерес возникает в первую очередь с точки зрения производительности, поскольку такие решения, как vector, string, wstring и т. Д., Обычно работают даже для взаимодействия с C API. Я рекомендую научиться использовать их и как использовать их эффективно. Если вам это действительно нужно, вы даже можете написать свой собственный распределитель памяти, чтобы сделать их очень быстрыми. Если вы уверены, что это не то, что вам нужно, по-прежнему нет оправдания тому, чтобы не написать простую оболочку для обработки этих строковых буферов с помощью RAII для динамических случаев.

С этим не связано:

Передает буфер как & buffer [0] лучший стиль программирования, чем сдача буфер? (Я предпочитаю & buffer [0].)

Нет.Я бы счел этот стиль немного менее полезным (по общему признанию, здесь субъективным), поскольку вы не можете использовать его для передачи нулевого буфера и, следовательно, вам придется делать исключения в своем стиле для передачи указателей на массивы, которые могут быть нулевыми. Однако это необходимо, если вы передаете данные из std :: vector в C API, ожидая указателя.

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

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

Статический буфер (статический символ buffer [N];) быстрее? Есть ли другие аргументы за или против?

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

Как насчет использования статического буфера char * = новый символ [N]; и никогда не удаляя буфер? (Повторное использование одного и того же буфера каждый call.)

У нас все еще есть те же проблемы с повторным входом.

Я понимаю, что выделение кучи следует использовать при (1) работе с большие буферы или (2) максимальный буфер размер неизвестен во время компиляции. Находятся Есть ли другие факторы, которые играют в решение о выделении стека / кучи?

Раскрутка стека разрушает объекты в стеке. Это особенно важно для безопасности исключений.Таким образом, даже если вы выделяете память в куче внутри функции, она обычно должна управляться объектом в стеке (например, интеллектуальным указателем). /// @ см. RAII.

Если вы предпочитаете sprintf_s, memcpy_s, ... варианты? (Visual Studio пытался убедить меня в этом давно, но хочу секунду мнение: p)

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

При использовании статических буферов вы можете использовать Тип возвращаемого значения const char *. Это (в целом) хорошая или плохая идея? (Я понимаете, что звонящему понадобится сделать свою копию, чтобы избежать следующий звонок изменит предыдущий возвращаемое значение.)

Я бы сказал, что почти в каждом случае вы хотите использовать const char * для типов, возвращаемых функцией, возвращающей указатель на символьный буфер. Функция, возвращающая изменяемый char *, обычно сбивает с толку и проблематична. Либо он возвращает адрес к глобальным / статическим данным, которые он не должен использовать в первую очередь (см. Повторный вход выше), либо локальные данные класса (если это метод), и в этом случае его возвращение разрушает способность класса к поддерживать инварианты, позволяя клиентам вмешиваться в нее, как им нравится (например, сохраненная строка всегда должна быть действительной), или возвращая память, указанную указателем, переданным в функцию (единственный случай, когда можно разумно утверждать, что изменяемый char * должны быть возвращены).

5
ответ дан 5 December 2019 в 07:10
поделиться
  1. Держитесь подальше от статических буферов, если вы когда-нибудь захотите повторно использовать свой код.

  2. используйте snprintf () вместо sprintf (), чтобы вы могли контролировать переполнение буфера.

  3. Вы никогда не знаете, сколько места в стеке осталось в контексте вашего вызова - поэтому никакой размер технически «безопасен».У вас есть много свободного места, чтобы играть большую часть времени. Но от этого одного раза тебе станет лучше. Я использую эмпирическое правило: никогда не помещать массивы в стек.

  4. Пусть клиент владеет буфером и передает его и его размер вашей функции. Это делает его повторно используемым и не оставляет двусмысленности в отношении того, кому нужно управлять сроком службы буфера.

  5. Если вы имеете дело со строковыми данными, дважды проверьте строковые функции, чтобы убедиться, что они завершаются, особенно когда достигают конца буфера. Библиотека C очень непоследовательна, когда дело доходит до обработки завершения строки в различных функциях.

8
ответ дан 5 December 2019 в 07:10
поделиться

У вас много вопросов! Я постараюсь ответить на пару вопросов и дать вам место для поиска остальных.

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

Да, но сам размер стека зависит от платформы, на которой вы работаете. См. Когда вы беспокоитесь о размере стека? , где есть очень похожий вопрос.

Буфер статических символов [N]; Быстрее? Находятся есть ли другие аргументы в пользу или против него?

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

Ответы на большинство других вопросов см. В Большие буферы против больших статических буферов, есть ли преимущество? .

3
ответ дан 5 December 2019 в 07:10
поделиться

1) buffer и &buffer[0] должны быть эквивалентны.

2) Ограничения на размер стека зависят от вашей платформы. Для большинства простых функций мое личное эмпирическое правило: все, что превышает ~256 КБ, объявляется динамически; в этом числе нет ни рифмы, ни причины, это просто моя собственная конвенция, и в настоящее время оно находится в пределах размеров стека по умолчанию для всех платформ, для которых я разрабатываю.

3) Статические буферы не быстрее и не медленнее (для всех намерений и целей). Единственное различие заключается в механизме контроля доступа. Компилятор обычно помещает статические данные в отдельную секцию двоичного файла, чем нестатические данные, но заметного/значительного выигрыша или штрафа в производительности нет. Единственный реальный способ узнать наверняка - написать программу в обоих вариантах и засечь время (поскольку многие аспекты скорости зависят от вашей платформы/компилятора).

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

5) Повторное использование буфера может привести к повышению производительности при работе с большими буферами за счет обхода накладных расходов, связанных с вызовом malloc/free или new/delete каждый раз. Однако вы рискуете случайно использовать старые данные, если забудете каждый раз очищать буфер или попытаетесь запустить две копии функции параллельно. Опять же, единственный реальный способ узнать наверняка - попробовать оба способа и измерить, сколько времени занимает выполнение кода.

6) Еще одним фактором при распределении стека/кучи является область видимости. Стековая переменная выходит из области видимости, когда функция, в которой она находится, возвращается, но переменная, которая была динамически выделена на куче, может быть безопасно возвращена вызывающей стороне или получена при следующем вызове функции (а-ля strtok).

7) Я бы не рекомендовал использовать sprintf_s, memcpy_s и им подобные. Они не являются частью стандартной библиотеки и не переносимы. Чем больше вы используете эти функции, тем больше дополнительной работы у вас будет, когда вы захотите запустить свой код на другой платформе или использовать другой компилятор.

2
ответ дан 5 December 2019 в 07:10
поделиться

Если функция дает вам способ узнать, сколько символов она вернет, используйте его. Ваш образец GetCurrentDirectory является хорошим примером:

DWORD length = ::GetCurrentDirectory(0, NULL);

Затем вы можете использовать динамически распределенный массив (строку или вектор) для получения результата:

std::vector<TCHAR> buffer(length, 0);
// assert(buffer.capacity() >= length);  // should always be true
GetCurrentDirectory(length, &buffer[0]);
1
ответ дан 5 December 2019 в 07:10
поделиться
  • Buffer или &Buffer[0] - это точно то же самое. Вы даже можете написать Buffer+0. Лично я предпочитаю просто писать Buffer (и я думаю, что большинство разработчиков также предпочитают это), но это ваш личный выбор
  • Максимум зависит от того, насколько велик и глубок ваш стек. Если в стеке уже 100 функций, то максимальный размер будет меньше. Если вы можете использовать C++, вы можете написать класс буфера, который динамически выбирает, использовать ли стек (для малых размеров) или кучу (для больших размеров). Ниже приведен код.
  • Статический буфер быстрее, так как компилятор заранее резервирует для вас место. Буфер стека также является быстрым. Для стекового буфера приложение просто должно увеличить указатель стека. Для буфера кучи менеджер памяти должен найти свободное место, запросить у операционной системы новую память, затем снова освободить ее, сделать некоторые бухгалтерские записи, ...
  • Если возможно, используйте строки C++, чтобы избежать утечек памяти. В противном случае вызывающая программа должна знать, нужно ли ей после этого освобождать память или нет. Недостатком является то, что строки C++ медленнее статических буферов (поскольку они выделяются в куче).
  • Я бы не стал использовать выделение памяти для глобальных переменных. Когда вы собираетесь ее удалять? И можете ли вы быть уверены, что никакой другой глобальной переменной не понадобится выделенная память (и она будет использована до того, как будет выделен ваш статический буфер)?
  • Какой бы вид буфера вы ни использовали, постарайтесь скрыть его реализацию от вызывающей вас функции. Можно попробовать спрятать буфер-указатель в классе, чтобы класс помнил, динамически выделен буфер или нет (и, следовательно, должен удалять его в своем деструкторе или нет). После этого легко изменить тип буфера, чего нельзя сделать, если вы просто возвращаете char-pointer.
  • Лично я предпочитаю обычные варианты sprintf, но это, вероятно, потому, что у меня все еще много старого кода, и я не хочу смешанной ситуации. В любом случае, рассмотрите возможность использования snprintf, где вы можете передать размер буфера.

Код для динамического буфера стека/кучи:

template<size_t BUFSIZE,typename eltType=char>
class DynamicBuffer
   {
   private:
      const static size_t MAXSIZE=1000;
   public:
      DynamicBuffer() : m_pointer(0) {if (BUFSIZE>=MAXSIZE) m_pointer = new eltType[BUFSIZE];}
      ~DynamicBuffer() {if (BUFSIZE>=MAXSIZE) delete[] m_pointer;};
      operator eltType * () { return BUFSIZE>=MAXSIZE ? m_pointer : m_buffer; }
      operator const eltType * () const { return BUFSIZE>=MAXSIZE ? m_pointer : m_buffer; }
   private:
      eltType m_buffer[BUFSIZE<MAXSIZE?BUFSIZE:1];
      eltType *m_pointer;
   };
0
ответ дан 5 December 2019 в 07:10
поделиться
Является ли передача буфера в виде &buffer[0] лучшим стилем программирования, чем передача буфера? (Я предпочитаю &buffer[0].)

&buffer[0] делает код менее читабельным для меня. Мне приходится на секунду остановиться и задуматься, почему кто-то использовал его вместо того, чтобы просто передать buffer. Иногда приходится использовать &buffer[0] (если buffer является std::vector), но в остальном придерживайтесь стандартного стиля C.

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

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

Если я правильно понимаю MSDN, потоки в Windows по умолчанию имеют размер стека 1 МБ. Это можно настроить. Другие платформы имеют другие ограничения.

Является ли статический char buffer[N]; более быстрым? Есть ли другие аргументы за или против?

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

Использование static не является потокобезопасным, в то время как использование стека является таковым. Это огромное преимущество стека. (Даже если вы не думаете, что будете использовать многопоточность, зачем усложнять себе жизнь, если это изменится в будущем?)

При использовании статических буферов вы можете вернуть вашей функции тип возврата const char *. Хорошая ли это идея? (Я понимаю, что вызывающая функция должна будет сделать свою собственную копию, чтобы избежать того, что следующий вызов изменит предыдущее возвращаемое значение).

Корректность const - это всегда хорошо.

Возврат указателей на статические буферы чреват ошибками; последующий вызов может изменить его, другой поток может изменить его и т.д. Вместо этого используйте std::string или другую автоматически выделяемую память (даже если ваша функция должна иметь дело с буферами char, как в вашем примере GetCurrentDirectory)

Как насчет использования static char * buffer = new char[N]; и никогда не удалять буфер? (Повторное использование одного и того же буфера при каждом вызове.)

Менее эффективно, чем просто использовать static char buffer[N], поскольку требуется выделение кучи.

Я понимаю, что выделение кучи должно использоваться, когда (1) приходится иметь дело с большими буферами или (2) максимальный размер буфера неизвестен во время компиляции. Есть ли другие факторы, которые играют роль в принятии решения о распределении стека/кучи?

См. ответ Джастина Ардини.

Стоит ли предпочесть варианты sprintf_s, memcpy_s, ...? (Visual Studio уже давно пытается убедить меня в этом, но я хочу услышать второе мнение :p )

Этот вопрос вызывает некоторые споры. Лично я считаю, что эти функции - хорошая идея, и если вы ориентируетесь исключительно на Windows, то есть некоторая польза в том, чтобы использовать предпочтительный для Windows подход и использовать эти функции. (И их довольно просто реализовать заново, если впоследствии вам понадобится что-то другое, кроме Windows, если вы не будете полагаться на их поведение при обработке ошибок). Другие считают, что функции Secure CRT не более безопасны, чем правильно используемый C, и вносят другие недостатки; Википедия ссылается на несколько аргументов против них.

1
ответ дан 5 December 2019 в 07:10
поделиться

Является ли передача буфера как & buffer [0] лучшим стилем программирования, чем передача буфера? (Я предпочитаю & buffer [0].)

Это зависит от стандартов кодирования. Лично я предпочитаю: буфер + индекс вместо & buffer [index] , но это дело вкуса.

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

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

Буфер статических символов [N]; Быстрее? Есть ли другие аргументы за или против?

Да, это должно быть быстрее. См. Также этот вопрос: Это плохая практика - объявлять промежуточную функцию массива

При использовании статических буферов вы можете получить возвращаемую функцию с типом const char *. Это хорошая идея? (Я понимаю, что вызывающий должен будет сделать свою копию, чтобы избежать того, что следующий вызов изменит предыдущее возвращаемое значение.)

Не уверен, что означает статика в этом случае, но:

  1. Если переменная объявлена ​​в стеке ( char buf [100] ): Вы не должны , а возвращать ссылки на вещи, которые объявлены в стеке. Они будут удалены при следующем вызове / объявлении функции (например, при повторном использовании стека).

  2. Если переменная объявлена ​​как static static , это сделает ваш код нереентерабельным. strtok является примером в этом случае.

Как насчет использования static char * buffer = new char [N]; и никогда не удаляя буфер? (Повторное использование одного и того же буфера при каждом вызове.)

Это возможно, но не рекомендуется, потому что это делает ваш код нереентерабельным .

Я понимаю, что выделение кучи следует использовать, когда (1) работают с большими буферами или (2) максимальный размер буфера неизвестен во время компиляции. Есть ли какие-либо другие факторы, влияющие на решение о выделении стека / кучи?

Размер стека работающего потока слишком мал, чтобы соответствовать объявлению стека (упоминалось ранее).

Что вы предпочитаете: sprintf_s, memcpy_s, ... варианты? (Visual Studio давно пытается убедить меня в этом, но мне нужно второе мнение: p)

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

// this is not tested - it is just an example
#ifdef _WINDOWS
 #define SPRINTF sprintf_s
#else
 #define SPRINTF sprintf
#endif
0
ответ дан 5 December 2019 в 07:10
поделиться
  • Это зависит от вас, просто выполнение buffer более кратко, но если бы это был вектор , вам нужно было бы выполнить & buffer [0] в любом случае.
  • Зависит от вашей предполагаемой платформы.
  • Имеет ли это значение? Вы определили, что это проблема? Напишите код, который легче всего читать и поддерживать, прежде чем вы перестанете беспокоиться, сможете ли вы превратить его во что-то более быстрое. Но как бы то ни было, выделение в стеке происходит очень быстро (вы просто меняете значение указателя стека).
  • Вы должны использовать std :: string . Если производительность станет проблемой, вы сможете уменьшить динамическое распределение, просто вернув внутренний буфер. Но интерфейс возврата std :: string намного приятнее и безопаснее, а производительность - ваша последняя забота.
  • Это утечка памяти. Многие будут утверждать, что это нормально, поскольку ОС все равно бесплатна, но я считаю, что просто утечка информации - ужасная практика. Используйте статический std :: vector , вы не должны никогда делать какое-либо необработанное выделение! Если вы ставите себя в положение, в котором может произойти утечка (потому что это должно быть сделано явно), вы делаете это неправильно.
  • Я думаю, что ваши (1) и (2) как раз покрывают это. Динамическое выделение почти всегда медленнее, чем выделение стека, но вам следует больше беспокоиться о том, что имеет смысл в вашей ситуации.
  • Тебе вообще не следует их использовать.Используйте std :: string , std :: stringstream , std :: copy и т. Д.
3
ответ дан 5 December 2019 в 07:10
поделиться