2010-06-24 2 views
7

В 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].)
  • Существует ли максимальный размер, который считается безопасным для стеков, выделенных буферами?
    • Update: Я имею в виду, например, самое высокое значение, которое можно считать безопасным для кросс-платформенных настольных приложений на Mac, Windows, Linux настольных компьютеров (не мобильный!).
  • Статический буфер (static char buffer[N];) быстрее? Есть ли другие аргументы в пользу или против?
  • При использовании статических буферов вы можете использовать тип возврата const char *. Это (вообще) хорошая или плохая идея? (Я понимаю, что вызывающему нужно будет сделать свою собственную копию, чтобы избежать того, что следующий вызов изменит предыдущее возвращаемое значение.)
  • Как насчет использования static char * buffer = new char[N];, никогда не удаляя буфер и не используя его повторно при каждом вызове.
  • Я понимаю, что распределение кучи должно использоваться, когда (1) работает с большими буферами или (2) максимальный размер буфера неизвестен во время компиляции. Есть ли другие факторы, которые играют в решении о распределении стека/кучи?
  • Вы предпочитаете sprintf_s, memcpy_s, ... варианты? (Visual Studio пытается убедить меня в этом в течение долгого времени, но я хочу, второе мнение: р)
+0

Что случилось с boost :: filesystem? –

+0

Вы забыли пару строк: 'userName.resize (1024, 'A'); userName.insert (0, «world of buffer overflows!»); ' – bk1e

ответ

4

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

С этим из пути:

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

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

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

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

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

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

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

У нас все еще есть те же проблемы с повторным подключением.

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

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

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

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

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

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

3
  • Это до вас, просто делает buffer более лаконична, но если бы это было vector , вам все равно нужно будет делать &buffer[0].
  • Зависит от вашей предполагаемой платформы.
  • Имеет ли это значение? Вы решили, что это проблема? Напишите код, который проще всего читать и обслуживать, прежде чем уйти от беспокойства, если вы можете запутать его в нечто более быстрое. Но для чего это стоит, выделение в стеке очень быстро (вы просто изменяете значение указателя стека.)
  • Вы должны использовать std::string. Если производительность становится проблемой, вы сможете уменьшить динамические распределения, просто вернув внутренний буфер. Но интерфейс возврата намного лучше и безопаснее, а производительность - ваша последняя проблема.
  • Это утечка памяти. Многие будут утверждать, что все в порядке, поскольку OS бесплатно это так или иначе, но я чувствую, что ужасная практика - просто утечка вещей. Используйте статический std::vector, вы должны никогда не делать какое-либо сырое распределение! Если вы ставите себя в положение, когда вы можете просочиться (потому что это должно быть сделано явно), вы делаете это неправильно.
  • Я думаю, что ваши (1) и (2) почти покрывают его. Динамическое распределение почти всегда медленнее, чем распределение стека, но вы должны быть более обеспокоены тем, что имеет смысл в вашей ситуации.
  • Вы не должны использовать их вообще. Используйте std::string, std::stringstream, std::copy и т.д.
+0

Я всегда забываю, что я могу использовать std :: vector a, создавая буфер с malloc/new. Спасибо, что напомнили мне :) Я согласен, что конструкции C++ должны быть предпочтительными, но мне нравится делать исключение для sprintf/printf. Я нахожу его более изящным, чем мусор мой код с потоковыми операторами. – StackedCrooked

+0

@Stack: тогда вы должны использовать Boost.Format или обернуть вещи в функцию, например 'boost :: lexical_cast'. – GManNickG

3

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

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

Да, но размер стека зависит от платформы, на которой вы работаете. См. When do you worry about stack size? для очень похожего вопроса.

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

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

Для получения ответов на большинство ваших других вопросов см. Large buffers vs Large static buffers, is there an advantage?.

8
  1. Держитесь подальше от статических буферов, если вы когда-либо захотите использовать свой код повторно.

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

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

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

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

+0

Не могу договориться о # 3. Знай свой код!Без буферов, распределенных по стекам, вам всегда нужно использовать мьютекс в многопоточном приложении, и я лично стараюсь как можно быстрее избежать мьютекса, или, другими словами, написать код без блокировки. Производительность гарантирована. Если вы не знакомы с поведением вашего приложения, вы столкнулись с проблемой (: – Poni

+0

@Poni: блокировка Mutex не единственная альтернатива стековым буферам при сохранении повторного входа. Вы можете передавать буферы от вызывающего, использовать кучу напрямую , или использовать пулы предварительно выделенных буферов кучи. –

+0

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

0
  • буфера или буфера & [0] точно так же. Вы даже можете написать Buffer + 0. Лично я предпочитаю просто писать Buffer (и я думаю, что большинство разработчиков также предпочитают это), но это ваш личный выбор
  • Максимальное значение зависит от того, насколько велика и насколько глубока ваш стек. Если у вас уже 100 функций в глубине стека, максимальный размер будет меньше.Если вы можете использовать C++, вы можете написать класс буфера, который динамически выбирает, использовать ли стек (для небольших размеров) или кучу (для больших размеров). Вы найдете код ниже.
  • Статический буфер быстрее, поскольку компилятор зарезервирует для вас пространство заранее. Буфер стека также выполняется быстро. Для стекового буфера приложение просто должно увеличить указатель стека. Для буфера кучи менеджер памяти должен найти свободное пространство, запросить операционную систему для новой памяти, затем снова освободить ее, выполнить некоторую бухгалтерию, ...
  • Если возможно, используйте строки C++, чтобы избежать утечек памяти. В противном случае вызывающий должен знать, должен ли он освобождать память после этого или нет. Недостатком является то, что строки C++ медленнее, чем статические буферы (поскольку они выделены в куче).
  • Я бы не использовал распределение памяти для глобальных переменных. Когда вы собираетесь удалить его? И можете ли вы быть уверены, что никакая другая глобальная переменная не понадобится выделенной памяти (и будет использоваться до выделения вашего статического буфера)?
  • Какой бы буфер вы ни использовали, попробуйте скрыть реализацию от вызывающей функции. Вы можете попытаться скрыть указатель буфера в классе, чтобы класс помнил, динамически распределен буфер или нет (и, следовательно, он должен удалить его в своем деструкторе или нет). Впоследствии легко изменить тип буфера, который вы не можете сделать, если вы просто вернете указатель символов.
  • Лично я предпочитаю обычные варианты 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; 
    }; 
1
Проходит буфер, как и буфер [0] лучший стиль программирования, чем прохождение буфера? (Я предпочитаю & buffer [0].)

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

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

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

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

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

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

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

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

Const correctness всегда хорошо.

Возвратные указатели на статические буферы подвержены ошибкам; более поздний вызов может изменить его, другой поток может изменить его и т.д. Используйте std::string вместо или другой памяти автоматически выделяется вместо (даже если ваша функция должна внутренне иметь дело с гольцов буферов, таких, как ваш 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 и ввести другие недостатки; Wikipedia links несколько аргументов против них.

0

Is passing the buffer as &buffer[0] better programming style than passing buffer? (I prefer &buffer[0].)

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

Is there a maximum size that is considered safe for stack allocated buffers?

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

Is static char buffer[N]; faster? Are there any other arguments for or against it?

Да, это должно быть быстрее. Смотрите также вопрос: Is it bad practice to declare an array mid-function

When using static buffers you can have your function return have the const char * return type. Is this a good idea? (I do realize that the caller will need to make his own copy to avoid that the next call would change the previous return value.)

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

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

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

What about using static char * buffer = new char[N]; and never deleting the buffer? (Reusing the same buffer each call.)

Это возможность, хотя и не рекомендуется, так как это делает ваш код non-reentrant.

I understand that heap allocation should be used when (1) dealing with large buffers or (2) maximum buffer size is unknown at compile time. Are there any other factors that play in the stack/heap allocation decision?

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

Should you prefer the sprintf_s, memcpy_s, ... variants? (Visual Studio has been trying to convince me of this for a long time, but I want a second opinion :p)

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

// this is not tested - it is just an example 
#ifdef _WINDOWS 
#define SPRINTF sprintf_s 
#else 
#define SPRINTF sprintf 
#endif 
+0

Ваша идея mcaro для переносимости не работает, так как большинство версий _s принимают дополнительные аргументы. – smerlin

1

Если функция дает метод зная, сколько символов он вернется, используйте его. Ваш образец 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

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

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

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

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

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

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

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

+0

Конечно, комментарий в # 6 не применяется к переменным стека, объявленным как 'static'. – bta