Какой самый безопасный способ передачи строк в C? - PullRequest
5 голосов
/ 15 апреля 2010

У меня есть программа на C, использующая Solaris с ОЧЕНЬ древней совместимостью. Многие примеры, даже здесь, на SO, не работают, а также большой код, который я написал для Mac OS X.

Так что, при использовании очень строгого C, какой самый безопасный способ передачи строк?

В настоящее время я использую указатели на символы повсюду, из-за того, что я считал простотой. Итак, у меня есть функции, которые возвращают char *, я передаю им char * и т. Д.

Я уже вижу странное поведение, такое как символ * Я передал значение, правильное при вводе функции, а затем значение загадочным образом исчезло ИЛИ повреждено / перезаписано после чего-то простого, например одного printf () или malloc to какой-то другой указатель.

Один из подходов к функциям, который, я уверен, неверен, может быть:

char *myfunction(char *somestr) {    
  char localstr[MAX_STRLENGTH] = strcpy(localstr, somestr);
  free(somestr);
  /* ... some work ... */
  char *returnstr = strdup(localstr);
  return returnstr;
}

Это кажется ... небрежным. Кто-нибудь может указать мне в правильном направлении простое требование?

Обновление

Один пример функции, когда я в растерянности из-за происходящего. Не уверен, что этого достаточно, чтобы понять это, но здесь говорится: '

char *get_fullpath(char *command, char *paths) {
  printf("paths inside function %s\n", paths); // Prints value of paths just fine

  char *fullpath = malloc(MAX_STRLENGTH*sizeof(char*));

  printf("paths after malloc %s\n", paths); // paths is all of a sudden just blank
}

Ответы [ 3 ]

12 голосов
/ 15 апреля 2010

Хорошо написанный код C придерживается следующего соглашения:

  • Все функции возвращают код состояния типа int , где возвращаемое значение 0 означает успех, а -1 означает сбой. В случае сбоя функция должна установить errno с соответствующим значением (например, EINVAL).
  • Значения, которые «сообщаются» функцией, должны передаваться через использование «out-параметров». Другими словами, один из параметров должен быть указателем на целевой объект.
  • Владение указателями должно принадлежать вызывающему; следовательно, функция не должна free какой-либо из своих параметров и должна только free объекты, которые она сама выделяет с помощью malloc/calloc.
  • Строки должны передаваться либо как const char* объекты, либо как char* объекты, в зависимости от того, должна ли строка быть перезаписана. Если строка не подлежит изменению, следует использовать const char*.
  • Всякий раз, когда передается массив, который не является строкой с NUL-окончанием, должен быть указан параметр, указывающий количество элементов в массиве или емкость этого массива.
  • Когда в функцию передается модифицируемый объект строки / буфера (т.е. char*), и эта функция предназначена для перезаписи, добавления или иного изменения строки, параметр, указывающий емкость строки / буфера, должен быть предоставляется (чтобы обеспечить динамический размер буфера и избежать переполнения буфера).

Я должен отметить, что в вашем примере кода вы возвращаете localstr, а не returnstr. Следовательно, вы возвращаете адрес объекта в кадре стека текущей функции. Кадр стека текущей функции исчезнет, ​​как только функция вернется. Вызов другой функции сразу после этого, вероятно, изменит данные в этом месте, что приведет к повреждению, которое вы наблюдали. Возвращение адреса локальной переменной приводит к «неопределенному поведению» и является неправильным.

Редактировать
Исходя из вашего обновленного кода (get_fullpath), становится ясно, что проблема не в вашей функции get_fullpath, а в функции, которая ее вызывает. Скорее всего, переменная paths предоставляется функцией, которая возвращает адрес локальной переменной. Следовательно, когда вы создаете локальную переменную в get_fullpath, она использует то же самое точное местоположение в стеке, что и ранее занятые пути. Так как "paths" является псевдонимом "fullpaths", он в основном перезаписывается адресом буфера, который вы установили неправильно, который является пустым.

Редактировать 2
Я создал страницу C Coding Conventions на моем веб-сайте с более подробными рекомендациями, пояснениями и примерами написания кода на C, если вам это интересно. Кроме того, утверждение, что localstr возвращается вместо returnstr, больше не соответствует действительности, так как вопрос был отредактирован в последний раз.

4 голосов
/ 15 апреля 2010

Вы не можете вернуть указатель на массив, который размещен локально внутри функции. Как только функция вернется, этот массив будет засорен.

Кроме того, когда вы ставите

char localstr[MAX_STRLENGTH] = strcpy(localstr, somestr);

что происходит, так это то, что strcpy () копирует байты в массив localstr [], но затем происходит ненужное назначение. Вы, вероятно, могли бы получить желаемый эффект в виде двух строк, таким образом ..

char localstr[MAX_STRLENGTH];
strcpy(localstr, somestr);

Также плохо вставлять вызов free () в такую ​​функцию. В идеале free () должна быть видна на том же уровне области, где была обнаружена malloc (). По той же логике немного сомнительно выделять память в функции таким образом.

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

// use a prototype like this to use the same buffer for both input and output
int modifyMyString(char buffer[], int bufferSize) {
    // .. operate you find in buffer[],
    //    leaving the result in buffer[]
    //    and be sure not to exceed buffer length
    // depending how it went, return EXIT_FAILURE or maybe
    return EXIT_SUCCESS;

// or separate input and outputs
int workOnString(char inBuffer[], int inBufSize, char outBuffer[], int outBufSize) {
    // (notice, you could replace inBuffer with const char *)
    // leave result int outBuffer[], return pass fail status
    return EXIT_SUCCESS;

Отсутствие встраивания внутри malloc () или free () также поможет избежать утечек памяти.

0 голосов
/ 15 апреля 2010

Ваш пример "обновления" завершен? Я не думаю, что это скомпилируется: оно требует возвращаемого значения, но вы никогда ничего не возвращаете. Вы никогда ничего не будете делать полным ходом, но, возможно, это преднамеренно, может быть, ваша точка зрения состоит в том, чтобы просто сказать, что когда вы делаете malloc, другие вещи ломаются.

Не видя звонящего, невозможно однозначно сказать, что здесь происходит. Я предполагаю, что paths - это динамически распределенный блок, который был свободен до того, как вы вызвали эту функцию. В зависимости от реализации компилятора может показаться, что блок free'd может содержать действительные данные до тех пор, пока будущий malloc не займет место.

Обновление: чтобы ответить на вопрос

Обработка строк - хорошо известная проблема в C. Если вы создаете массив фиксированного размера для хранения строки, вам нужно беспокоиться о длинной строке, переполняющей выделенное пространство. Это означает постоянную проверку размеров строк на копиях с использованием strncpy и strncat вместо простых strcpy и strcat или аналогичных методов. Вы можете пропустить это и просто сказать: «Ну, ни у кого никогда не будет имени длиннее 60 символов» или чего-то подобного, но всегда есть опасность, что кто-то это сделает. Даже для чего-то, что должно иметь известный размер, например, номер социального страхования или номер ISBN, кто-то может ошибиться, введя его и дважды нажав клавишу, или злонамеренный пользователь может намеренно ввести что-то длинное. И т.д. Конечно, это в основном проблема с вводом данных или чтением файлов. Если у вас есть строка в поле некоторого известного размера, то для любых копий или других манипуляций вы знаете размер.

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

if (strcmp(getMeSomeString(),stringIWantToCompareItTo)==0) etc

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

char* someString=getMeSomeString();
int f=strcmp(someString,stringIWantToCompareItTo);
free(someString);
if (f==0)
etc

Итак, все работает, но читабельность просто упала.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...