передать строки по ссылке в C - PullRequest
8 голосов
/ 08 декабря 2009

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

Я сделал это вики сообщества, поэтому не стесняйтесь редактировать.

P.S. Это не домашнее задание.

Это оригинальная версия

#include <stdio.h>

#include <stdlib.h>
#include <string.h>

void
fn(char *baz, char *foo, char *bar)
{
     char *pch;

     /* this is the part I'm having trouble with */

     pch = strtok (baz, ":");
     foo = malloc(strlen(pch));
     strcpy(foo, pch);

     pch = strtok (NULL, ":");
     bar = malloc(strlen(pch));
     strcpy(bar, pch);

     return;
}

int
main(void)
{
     char *mybaz, *myfoo, *mybar;

     mybaz = "hello:world";

     fn(mybaz, myfoo, mybar);

     fprintf(stderr, "%s %s", myfoo, mybar);
}

ОБНОВЛЕНИЕ Вот обновленная версия с некоторыми из реализованных предложений:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAXLINE         1024

void
fn(char *baz, char **foo, char **bar)
{
     char line[MAXLINE];
     char *pch;

     strcpy(line, baz);

     pch = strtok (line, ":");
     *foo = (char *)malloc(strlen(pch)+1);
     (*foo)[strlen(pch)] = '\n';
     strcpy(*foo, pch);

     pch = strtok (NULL, ":");
     *bar = (char *)malloc(strlen(pch)+1);
     (*bar)[strlen(pch)] = '\n';
     strcpy(*bar, pch);

     return;
}

int
main(void)
{
     char *mybaz, *myfoo, *mybar;

     mybaz = "hello:world";

     fn(mybaz, &myfoo, &mybar);

     fprintf(stderr, "%s %s", myfoo, mybar);

     free(myfoo);
     free(mybar);
}

Ответы [ 8 ]

9 голосов
/ 08 декабря 2009

Во-первых, эти mallocs должны быть для strlen(whatever)+1 байтов. Строки C имеют символ 0 для обозначения конца, называемого терминатором NUL, и они не включены в длину, измеряемую в strlen.

Далее, strtok изменяет строку, которую вы ищете. Вы передаете ему указатель на строку, которую вы не можете изменять (вы не можете изменять буквенные строки). Это может быть причиной ошибки. Таким образом, вместо использования указателя на неизменяемый строковый литерал, вы можете скопировать его в свой модифицируемый буфер, например так:

char mybaz[] = "hello:world";

Для этого нужно поместить в стек массив размером 12 символов и скопировать байты строкового литерала в этот массив. Это работает, потому что компилятор знает, во время компиляции, какова длина строки, и может соответственно освободить место. Это экономит использование malloc для этой конкретной копии.

Проблема со ссылками заключается в том, что в настоящее время вы передаете значение mybaz, myfoo и mybar в вашу функцию. Вы не можете изменить переменные вызывающего, если вы не передадите указатель в myfoo и mybar. Поскольку myfoo - это символ *, указатель на него - это символ **:

void
fn(char *baz, char **foo, char **bar) // take pointers-to-pointers

*foo = malloc(...);  // set the value pointed to by foo

fn(mybaz, &myfoo, &mybar);  // pass pointers to myfoo and mybar

Изменение foo в функции в вашем коде абсолютно не влияет на myfoo. myfoo неинициализирован, поэтому, если ни одна из первых двух причин его не вызывает, то, скорее всего, происходит сбой, когда вы приходите на печать с использованием этого неинициализированного указателя.

Как только у вас все получится, вы можете добавить обработку ошибок. strtok может вернуть NULL, если не найдет искомый разделитель, и вы не можете вызвать strlen с NULL. malloc может вернуть NULL, если недостаточно памяти, и вы не можете также вызвать strcpy с NULL.

2 голосов
/ 08 декабря 2009

Одна вещь, которую все пропускают, это то, что вы вызываете strtok для массива, хранящегося в постоянной памяти. strtok записывает в передаваемый вами массив, поэтому убедитесь, что вы скопировали его во временный массив, прежде чем вызывать на него strtok, или просто выделите исходный, например:

char mybaz[] = "hello:world";
1 голос
/ 08 декабря 2009

О, да, там небольшая проблема.

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

Вы пошли по более универсальному, но чуть более сложному маршруту: вы используете malloc(), чтобы создать место для ваших результатов (пока что хорошо!), А затем попытаетесь назначить пространство malloc для указателей, которые вы передаете. Это, увы, не сработает.

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

Если вы получили это, отлично. Если нет, просьба дать дополнительные разъяснения.

1 голос
/ 08 декабря 2009

Вы хотите передать обратно 2 указателя. Так что вам нужно назвать его с помощью пары указателей на указатели. Примерно так:

void
fn(char *baz, char **foo, char **bar) {
   ...
   *foo = malloc( ... );
   ...
   *bar = malloc( ... );
   ...
}
1 голос
/ 08 декабря 2009

В C вы обычно передаете по ссылке, передавая 1) указатель первого элемента массива и 2) длину массива.

Длина массива иногда может быть опущена, если вы уверены в размере буфера, и можно узнать длину строки, если искать символ с нулевым символом в конце (символ со значением 0 или '\0'.

Из вашего примера кода видно, что вы пытаетесь установить значение, на которое указывает указатель. Так что вы, вероятно, хотите указатель char**. И вы передадите адрес вашей переменной char*, которую вы хотите установить.

0 голосов
/ 08 декабря 2009

Существенная проблема заключается в том, что, хотя хранилище когда-либо выделяется (с malloc()) для результатов, которые вы пытаетесь вернуть как myfoo и mybar, указатели на эти распределения фактически не возвращаются в main(). В результате, более поздний вызов printf() вполне может вызвать дамп ядра.

Решение состоит в том, чтобы объявить аргументы как указатели на указатель на char и передать адреса myfoo и mybar на fn. Нечто подобное (непроверенное) должно сработать:

void
fn(char *baz, char **foo, char **bar)
{
     char *pch;

     /* this is the part I'm having trouble with */

     pch = strtok (baz, ":");
     *foo = malloc(strlen(pch)+1);  /* include space for NUL termination */
     strcpy(*foo, pch);

     pch = strtok (NULL, ":");
     *bar = malloc(strlen(pch)+1);  /* include space for NUL termination */
     strcpy(*bar, pch);

     return;
}

int
main(void)
{
     char mybaz[] = "hello:world";
     char *myfoo, *mybar;

     fn(mybaz, &myfoo, &mybar);
     fprintf(stderr, "%s %s", myfoo, mybar);
     free(myfoo);
     free(mybar);
}

Не забудьте освободить каждую выделенную строку на более позднем этапе, иначе вы создадите утечки памяти.

Чтобы одновременно выполнять malloc () и strcpy () в одном вызове, было бы лучше использовать strdup(), поскольку он также помнит, что нужно выделить место для завершающего NUL, который вы оставили в коде, как написано. *foo = strdup(pch) гораздо понятнее и легче поддерживать эту альтернативу. Поскольку strdup() - это POSIX, а не ANSI C, вам, возможно, потребуется реализовать его самостоятельно, но усилия хорошо окупаются полученной ясностью для этого вида использования.

Другим традиционным способом возврата строки из функции C является то, чтобы вызывающая сторона выделяла хранилище и предоставляла его адрес функции. Это техника, используемая, например, sprintf(). Проблема состоит в том, что нет способа сделать такой сайт вызова полностью безопасным от ошибок переполнения буфера, вызванных вызываемой функцией, при условии, что было выделено больше места, чем фактически доступно. Традиционное исправление этой проблемы состоит в том, чтобы требовать, чтобы аргумент длины буфера также передавался, и тщательно проверять как фактическое выделение, так и длину, заявленную на сайте вызова при проверке кода.

Edit:

Фактический segfault, который вы получаете, скорее всего, находится внутри strtok(), а не printf(), потому что ваш записанный пример пытается передать строковую константу в strtok(), которая должна иметь возможность изменять строку. Это официально неопределенное поведение.

Исправление этой проблемы - убедиться, что bybaz объявлен как инициализированный массив, а не как указатель на char. Инициализированный массив будет расположен в доступной для записи памяти, а строковая константа, скорее всего, будет находиться в постоянной памяти. Во многих случаях строковые константы хранятся в той же части памяти, которая используется для хранения самого исполняемого кода, и все современные системы пытаются затруднить программе изменение собственного исполняемого кода.

Во встроенных системах, над которыми я зарабатываю на жизнь, код, вероятно, будет храниться в каком-либо ПЗУ и не может быть физически изменен.

0 голосов
/ 08 декабря 2009

Другие ответы описывают, как исправить ваш ответ, но простой способ выполнить то, что вы имеете в виду , - это strdup (), который выделяет новую память соответствующего размера и копирует правильные символы .

Тем не менее, нужно исправить бизнес с помощью char * vs char **. Обойти это просто невозможно.

0 голосов
/ 08 декабря 2009

код, скорее всего, segfaults, потому что вы выделяете место для строки, но забываете, что строка имеет дополнительный байт в конце, нулевой терминатор.

Кроме того, вы передаете только указатель. Поскольку указатель является 32-разрядным значением (на 32-разрядном компьютере), вы просто передаете значение унитизированного указателя в «fn». Точно так же вы бы не объяснили целое число, переданное в функцию для возврата в вызывающую функцию (без явного возврата), вы не можете ожидать, что указатель сделает то же самое. Таким образом, новые значения указателя никогда не возвращаются в основную функцию. Обычно вы делаете это, передавая указатель на указатель в C.

Также не забудьте освободить динамически выделенную память !!

void
fn(char *baz, char **foo, char **bar)
{
     char *pch;

     /* this is the part I'm having trouble with */

     pch = strtok (baz, ":");
     *foo = malloc(strlen(pch) + 1);
     strcpy(*foo, pch);

     pch = strtok (NULL, ":");
     *bar = malloc(strlen(pch) + 1);
     strcpy(*bar, pch);

     return;
}

int
main(void)
{
     char *mybaz, *myfoo, *mybar;

     mybaz = "hello:world";

     fn(mybaz, &myfoo, &mybar);

     fprintf(stderr, "%s %s", myfoo, mybar);

     free( myFoo );
     free( myBar );
}
...