Memcpy, строка и терминатор

Мне нужно написать функцию, которая заполняет буфер char * на заданную длину содержимым строки. Если строка слишком длинная, я просто должен обрезать ее. Буфер выделяется не мной, а пользователем моей функции. Я попробовал что-то вроде этого:

int writebuff(char* buffer, int length){
    string text="123456789012345";
    memcpy(buffer, text.c_str(),length);
    //buffer[length]='\0';
    return 1;
}


int main(){
    char* buffer = new char[10];
    writebuff(buffer,10);
    cout << "After: "<<buffer<<endl;
}

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

Любые намеки на правильную процедуру для подражания?

10 ответов

Решение

Строка в стиле C должна заканчиваться нулевым символом '\0',

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

PS Для полноты вот хорошая версия вашей функции. Спасибо Naveen за то, что он указал на ошибку off-by-one в вашем завершающем нуле. Я взял на себя смелость использовать ваше возвращаемое значение, чтобы указать длину возвращаемой строки или количество символов, необходимое, если переданная длина была <= 0.

int writebuff(char* buffer, int length)
{
    string text="123456789012345";
    if (length <= 0)
        return text.size();
    if (text.size() < length)
    {
        memcpy(buffer, text.c_str(), text.size()+1);
        return text.size();
    }
    memcpy(buffer, text.c_str(), length-1);
    buffer[length-1]='\0';
    return length-1;
}

Если вы хотите рассматривать буфер как строку, вы должны NULL завершить его. Для этого вам нужно скопировать length-1 использование символов memcpy и установить length-1 персонаж как \0,

Кажется, вы используете C++ - учитывая, что самый простой подход (при условии, что завершение NUL требуется спецификацией интерфейса)

int writebuff(char* buffer, int length)
{
  string text = "123456789012345";
  std::fill_n(buffer, length, 0); // reset the entire buffer
  // use the built-in copy method from std::string, it will decide what's best.
  text.copy(buffer, length);
  // only over-write the last character if source is greater than length
  if (length < text.size())
    buffer[length-1] = 0;
  return 1; // eh?
}

char * Буферы должны заканчиваться нулем, если вы явно не раздавали длину с ней везде и говорите так, чтобы буфер не заканчивался нулем.

Следует ли вам завершить строку с помощью \0 зависит от спецификации вашего writebuff функция. Если то, что у вас есть в buffer должна быть правильной строкой в ​​стиле C после вызова вашей функции, вы должны завершить ее \0,

Обратите внимание, что c_str() закончится с \0 для вас, чтобы вы могли использовать text.size() + 1 как размер исходной строки. Также обратите внимание, что если length больше, чем размер строки, вы будете копировать дальше, чем text предоставляет ваш текущий код (вы можете использовать min(length - 2, text.size() + 1/*trailing \0*/) чтобы предотвратить это, и установить buffer[length - 1] = 0 чтобы завершить это).

buffer выделено в main утечка, кстати

Во-первых, я не знаю, writerbuff должен завершать строку или нет. Это вопрос дизайна, на который должен ответить тот, кто решил, что writebuff должен существовать вообще.

Во-вторых, принимая ваш конкретный пример в целом, есть две проблемы. Во-первых, вы передаете неопределенную строку operator<<(ostream, char*), Во-вторых, закомментированная строка записывает за конец указанного буфера. Оба они вызывают неопределенное поведение.

(Третий - недостаток дизайна - знаете ли вы, что length всегда меньше длины text?)

Попробуй это:

int writebuff(char* buffer, int length){
  string text="123456789012345";
  memcpy(buffer, text.c_str(),length);
  buffer[length-1]='\0';
  return 1;
}


int main(){
  char* buffer = new char[10];
  writebuff(buffer,10);
  cout << "After: "<<buffer<<endl;
}

Я согласен с Некролисом, что strncpy - это путь, но он не получит нулевой терминатор, если строка слишком длинная. У вас была правильная идея поставить явный терминатор, но, как написано, ваш код ставит его за один конец. (Это на C, так как вы, кажется, делаете больше C, чем C++?)

int writebuff(char* buffer, int length){
    char* text="123456789012345";
    strncpy(buffer, text, length);
    buffer[length-1]='\0';
   return 1;
}
  1. В main(), вам следует delete буфер, который вы выделили new.или распределить его статически (char buf[10]). Да, это всего 10 байтов, и да, это "пул" памяти, а не утечка, так как это одноразовые распределения, и да, вам нужна эта память в течение всего времени работы программы. Но это все еще хорошая привычка.

  2. В C/C++ общий контракт с символьными буферами заключается в том, что они заканчиваются нулем, поэтому я включил бы его, если бы мне явно не сказали не делать этого. И если бы я это сделал, я бы прокомментировал это, и, возможно, даже использовал typedef или имя на char * параметр, указывающий, что результатом является строка, которая не оканчивается нулем.

Мой вопрос о терминаторе: он там или нет?

Да. Это должно быть там. Иначе как бы вы позже узнали, где заканчивается строка? И как бы cout знал бы? Он будет продолжать печатать мусор, пока не встретит мусор, значение которого оказывается \0, Ваша программа может даже потерпеть крах.

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

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

* если у вас возникли сомнения, пройдите самый безопасный путь!

Другие вопросы по тегам