2010-04-15 2 views
5

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

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

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

Я уже вижу странное поведение, как char * Я передал его значение правильно, когда я ввожу функцию, а затем значение таинственно исчезло или испорчено/перезаписано после чего-то простого, например, одного printf() или malloc другому указателю.

Один подход к функциям, которые я уверен, что это неправильно, может быть:

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

Update

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

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 
} 
+0

Я думаю, что больше похоже на то, что вы делаете то, что вызывает неопределенное поведение. Прежде чем обвинять его в компиляторе или ОС, я предлагаю вам поделиться с нами некоторым примером кода, чтобы мы могли сказать вам, действительно ли ваш исходный код, который работал с ОС X. –

+2

Это просто выглядит ... как минимум. Вы назначаете массив (???) ... который вы хотите вставить? У вас есть returnstr, но возвращайте localstr (в стек, oops!) И т. Д. В любом случае, добро пожаловать в забавный мир C. Собственность на объекты (да, C тоже их) должна быть четко определена. Например, что произойдет, если код выше вызывается как myfunction («Hello world!») - в любом случае, определите контракты. Один из подходов состоит в том, чтобы заставить CALLER передать действительный объект, способный принимать n символов (если требуется больше вызовов, и т. Д.) – 2010-04-15 00:12:26

+0

Я смущен относительно того, что означает «действительно строгий C». Я согласен с Майклом в том, что «действительно странное поведение», которое вы видите, - это просто неопределенное поведение, учитывая приведенный выше код. В C нет специального способа передать «строку», он работает так же, как и любой другой массив. Что именно вы делаете? –

ответ

12

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

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

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

Редактировать
На основе вашего обновленного кода (get_fullpath), то ясно, что проблема не в вашей функции get_fullpath, а в функции, которая зовет его. Скорее всего, переменная paths предоставляется функцией, которая возвращает адрес локальной переменной. Следовательно, когда вы создаете локальную переменную в get_fullpath, она использует то же точное местоположение в стеке, что и ранее занятые. Поскольку «пути» - это псевдонимы «fullpaths», они в основном перезаписываются адресом буфера, который вы malloced, который пуст.

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

+1

Ницца, мне этот список нравится. Спасибо за помощь! Мне нужно много узнать о том, как быть дисциплинированным ... – chucknelson

+1

+1 для вашего третьего пункта! – JustJeff

+1

Вот где я теряюсь, как «полные пути» даже касаются памяти, которая уже выделена и используется в «путях»? – chucknelson

4

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

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

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

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

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

Кроме того, это плохой форме, чтобы вставить свободный() вызов внутри функции, как это. В идеале 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; 

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

+0

Я делаю копию и указываю ее на returnstr, хотя ... разве это не устраняет проблему локального масштаба? – chucknelson

+0

@chucknelson: в примере это может быть опечатка, но возвращаемое является указателем на локальный массив, а не только выделенный блок, на который указывает 'returnstr'. –

+0

@MichaelBurr ack, вы правы, исправляя сейчас ... спасибо! – chucknelson

0

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

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

Update: на самом деле ответить на этот вопрос

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

Так хорошо, это работает, но читаемость просто plummetted.

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

+0

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