Эквивалент strncpy для std::string?

Существует ли точный эквивалент strncpy в стандартной библиотеке C++? Я имею в виду функцию, которая копирует строку из одного буфера в другой, пока не достигнет завершающего 0? Например, когда мне нужно анализировать строки из небезопасного источника, такого как пакеты TCP, поэтому я могу выполнять проверки длины при копировании данных.

Я уже много искал по этой теме, и я также нашел несколько интересных тем, но все эти люди были довольны std::string::assign, который также может принимать размер символов для копирования в качестве параметра. Моя проблема с этой функцией заключается в том, что она не выполняет никаких проверок, если уже был достигнут завершающий нуль - она ​​серьезно относится к заданному размеру и копирует данные так же, как это сделал бы memcpy в строковый буфер. . Таким образом выделяется и копируется гораздо больше памяти, чем нужно было бы, если бы была такая проверка при копировании.

Так я работаю над этой проблемой в настоящее время, но есть некоторые накладные расходы, которых я хотел бы избежать:

    // Get RVA of export name
    const ExportDirectory_t *pED = (const ExportDirectory_t*)rva2ptr(exportRVA);
    sSRA nameSra = rva2sra(pED->Name);

    // Copy it into my buffer
    char *szExportName = new char[nameSra.numBytesToSectionsEnd];
    strncpy(szExportName, 
            nameSra.pSection->pRawData->constPtr<char>(nameSra.offset),
            nameSra.numBytesToSectionsEnd);
    szExportName[nameSra.numBytesToSectionsEnd - 1] = 0;

    m_exportName = szExportName;
    delete [] szExportName;

Этот фрагмент кода является частью моего синтаксического анализатора PE-бинарников (точнее, подпрограммы, анализирующей экспортную таблицу). rva2sra преобразует относительный виртуальный адрес в относительный адрес PE-раздела. Структура ExportDirectory_t содержит RVA для имени экспорта двоичного файла, которое должно быть строкой, заканчивающейся нулем. Но это не всегда должно быть так - если кому-то этого захочется, он сможет опустить завершающий ноль, из-за чего моя программа запустится в памяти, которая не принадлежит разделу, где она, в конце концов, рухнет ( в лучшем случае...).

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


person athre0z    schedule 16.01.2012    source источник
comment
std::string не заканчиваются нулем. Вам нужна копия строки или просто копия всего до первого нулевого байта (который может там быть, а может и не быть)?   -  person Seth Carnegie    schedule 16.01.2012
comment
О, приятно знать. Я хотел бы скопировать все до первого нулевого байта.   -  person athre0z    schedule 16.01.2012
comment
Будьте осторожны с тем, что вы попросить.   -  person Keith Thompson    schedule 06.08.2014


Ответы (7)


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

const char[] buffer = "hello\0there";

std::string s(buffer);

// s contains "hello"

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

int len_of_buffer = something;
const char* buffer = somethingelse;

const char* copyupto = std::find(buffer, buffer + len_of_buffer, 0); // find the first NUL

std::string s(buffer, copyupto);

// s now contains all the characters up to the first NUL from buffer, or if there
// was no NUL, it contains the entire contents of buffer

Вы можете обернуть вторую версию (которая всегда работает, даже если в буфере нет NUL) в аккуратную маленькую функцию:

std::string string_ncopy(const char* buffer, std::size_t buffer_size) {
    const char* copyupto = std::find(buffer, buffer + buffer_size, 0);

    return std::string(buffer, copyupto);
}

Но одно замечание: если вы передадите конструктору с одним аргументом const char* сам по себе, он будет работать до тех пор, пока не найдет NUL. Важно, чтобы вы знали, что в буфере есть хотя бы один NUL, если вы используете конструктор с одним аргументом std::string.

К сожалению (или к счастью), идеального аналога strncpy для std::string не существует.

person Seth Carnegie    schedule 16.01.2012
comment
Спасибо, это то, что я искал. - person athre0z; 16.01.2012

Класс std::string в STL может содержать нулевые символы в строке ("xxx\0yyy" — это вполне допустимая строка длиной 7). Это означает, что он ничего не знает о нулевом завершении (ну почти, есть преобразования из/в строки C). Другими словами, в STL нет альтернативы для strncpy.

Есть несколько способов достичь своей цели с помощью более короткого кода:

const char *ptr = nameSra.pSection->pRawData->constPtr<char>(nameSra.offset);
m_exportName.assign(ptr, strnlen(ptr, nameSra.numBytesToSectionsEnd));

or

const char *ptr = nameSra.pSection->pRawData->constPtr<char>(nameSra.offset);
m_exportName.reserve(nameSra.numBytesToSectionsEnd);
for (int i = 0; i < nameSra.numBytesToSectionsEnd && ptr[i]; i++) 
  m_exportName += ptr[i];
person Karel Petranek    schedule 16.01.2012

Существует ли точный эквивалент strncpy в стандартной библиотеке C++?

Я конечно надеюсь, что нет!

Я имею в виду функцию, которая копирует строку из одного буфера в другой, пока не достигнет завершающего 0?

Ах, но это не то, что делает strncpy() — или, по крайней мере, это не все, что он делает.

strncpy() позволяет указать размер n целевого буфера и копирует не более n символов. Это хорошо, насколько это возможно. Если длина исходной строки ("длина" определяется как количество символов, предшествующих завершающей '\0') превышает n, буфер назначения дополняется дополнительными \0', что редко бывает полезно. А если длина исходной строки превышает n, то завершающая '\0' не копируется.

Функция strncpy() была разработана для того, чтобы ранние системы Unix сохраняли имена файлов в записях каталогов: в виде 14-байтового буфера фиксированного размера, который может содержать до 14 символов имени. (РЕДАКТИРОВАТЬ: я не уверен на 100%, что именно это послужило мотивом для его разработки.) Возможно, это не строковая функция и не просто «более безопасный» вариант strcpy().

Вы можете добиться эквивалента того, что, как можно было бы предположить, делает strncpy() (учитывая имя), используя strncat():

char dest[SOME_SIZE];
dest[0] = '\0';
strncat(dest, source_string, SOME_SIZE);

Это всегда будет '\0' завершать буфер назначения, и он не будет без необходимости дополнять его дополнительными '\0' байтами.

Вы действительно ищете std::string эквивалент этого?

EDIT: После того, как я написал выше, я опубликовал этот разглагольствования в моем блоге.

person Keith Thompson    schedule 16.01.2012
comment
Я знаю об этих фактах (кроме того, что касается заполнения нулями), но это была «лучшая» функция в стандартной библиотеке C/C++, которую я смог найти для удовлетворения своих потребностей. Как вы можете видеть в моем примере, приведенном в первом посте, я добавил завершающий нулевой байт вручную. Разве strncat не ищет первый нулевой байт в строке назначения, а затем начинает добавлять точно в ту позицию, в которой он нашел нулевой байт, проверяя, не превышена ли длина, и, наконец, добавляет завершающий нуль? Поэтому мне пришлось установить первый байт моего целевого буфера равным нулю вручную, я прав? - person athre0z; 17.01.2012

std::string имеет конструктор со следующей сигнатурой, которую можно использовать:

string ( const char * s, size_t n );

со следующим описанием:

Содержимое инициализируется копией строки, состоящей из первых n символов в массиве символов, на который указывает s.

person BЈовић    schedule 16.01.2012
comment
Обратите внимание, что это не остановится на нулевых байтах (это говорит о массиве символов, а не о C-строке). Это может быть или не быть тем, что вы хотите. - person Seth Carnegie; 16.01.2012
comment
Спасибо за ответ, но это не совсем то, что я хотел знать. Конструктор копирует все, пока не достигнет первого нулевого байта ИЛИ, если задан размер, все, как memcpy. Если в моей исходной строке есть нулевой байт, содержимое после него также копируется. - person athre0z; 16.01.2012
comment
@SethCarnegie Да, именно поэтому я добавил описание. Может быть, этот конструктор не то, что нужно OP - person BЈовић; 16.01.2012
comment
Это не уважает нулевых терминаторов и для меня довольно обманчивый конструктор. С помощью: const char arr[10] = Hello; std::string a(arr); std::string b(обр, 10); a != b, потому что b имеет 4 неинициализированных байта, включенных в конце, чтобы составить длину 10. Печально, что std::strings может содержать символы \0, что в большинстве случаев не имеет смысла. - person Jetski S-type; 13.05.2016

Нет встроенного аналога. Вы должны свернуть свой собственный strncpy.

#include <cstring>
#include <string>

std::string strncpy(const char* str, const size_t n)
{
    if (str == NULL || n == 0)
    {
        return std::string();
    }

    return std::string(str, std::min(std::strlen(str), n));
}
person Steve Ward    schedule 06.08.2014

Конструктор подстроки строки может делать то, что вы хотите, хотя он и не является точным эквивалентом strncpy (см. мои примечания в конце):

std::string( const std::string& other, 
          size_type pos, 
          size_type count = std::string::npos,
          const Allocator& alloc = Allocator() );

Создает строку с подстрокой [pos, pos+count) из other. Если count == npos или запрошенная подстрока выходит за конец строки, результирующая подстрока будет [pos, size()).

Источник: http://www.cplusplus.com/reference/string/string/string/

Пример:

#include <iostream>
#include <string>
#include <cstring>
int main ()
{
    std::string s0 ("Initial string");
    std::string s1 (s0, 0, 40); // count is bigger than s0's length
    std::string s2 (40, 'a');   // the 'a' characters will be overwritten
    strncpy(&s2[0], s0.c_str(), s2.size());
    std::cout << "s1: '" << s1 << "' (size=" << s1.size() << ")" << std::endl;
    std::cout << "s2: '" << s2 << "' (size=" << s2.size() << ")" << std::endl;
    return 0;
}

Выход:

s1: 'Initial string' (size=14)
s2: 'Initial string' (size=40)

Отличия от strncpy:

  • Строковый конструктор всегда добавляет к результату завершающий нуль символ, а strncpy — нет;
  • Строковый конструктор не дополняет результат нулями, если до запрошенного счетчика достигается символ, завершающий нулевое значение, это делает strncpy.
person Julien-L    schedule 27.09.2016

Используйте конструктор класса:

string::string str1("Hello world!");
string::string str2(str1);

Это даст точную копию в соответствии с этой документацией: http://www.cplusplus.com/reference/string/string/string/

person Community    schedule 16.01.2012
comment
ОП ищет не strcpy, а strncpy. - person pmr; 16.01.2012
comment
@pmr: OP пояснил, что ему нужен только первый NULL-байт. - person Mooing Duck; 16.01.2012
comment
Не защищает от чтения за пределами известных границ буфера. - person Jetski S-type; 13.05.2016