Memcpy, строка и терминатор - PullRequest
5 голосов
/ 10 мая 2011

Мне нужно написать функцию, которая заполняет буфер 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 ]

7 голосов
/ 10 мая 2011

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

6 голосов
/ 10 мая 2011

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

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

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

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;
}
2 голосов
/ 10 мая 2011

кажется, что вы используете 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?
}
1 голос
/ 10 мая 2011

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

0 голосов
/ 10 мая 2011
  1. В main() вы должны delete буфер, выделенный с помощью new., или статически (char buf[10]). Да, это всего 10 байтов, и да, это «пул» памяти, а не утечка, так как это одноразовые распределения, и да, вам нужна эта память в течение всего времени работы программы. Но это все еще хорошая привычка.

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

0 голосов
/ 10 мая 2011

Во-первых, я не знаю, должен ли 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;
}
0 голосов
/ 10 мая 2011

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

int writebuff(char* buffer, int length){
    char* text="123456789012345";
    strncpy(buffer, text, length);
    buffer[length-1]='\0';
   return 1;
}
0 голосов
/ 10 мая 2011

Завершение строки в \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, просочился, кстати

0 голосов
/ 10 мая 2011

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

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

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

0 голосов
/ 10 мая 2011

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

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

...