2009-12-07 1 views
7

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

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

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); 
} 

UPDATE Вот обновленная версия с некоторыми из предложений внедрено:

#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); 
} 
+0

для вашего strtok segfault посмотрите на мое предложение ниже – ThePosey

ответ

7

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

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

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 не инициализирован, поэтому, если ни одна из первых двух вещей не вызывает этого, segfault, скорее всего, возникает, когда вы приходите к печати с использованием этого неинициализированного указателя.

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

+0

Сег-ошибка, по-видимому, происходит на pch = strtok (baz, ":"); на самом деле ... Я все еще пытаюсь понять, почему – Jenna

+0

Да, извините. Я только заметил свою «вторую вещь» после первых двух версий моего ответа. Надеюсь, я объяснил это. –

1

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

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

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

1

Вы хотите передать 2 указателя, поэтому вам нужно называть это парами указателей на указатели. Что-то вроде этого :

void 
fn(char *baz, char **foo, char **bar) { 
    ... 
    *foo = malloc(...); 
    ... 
    *bar = malloc(...); 
    ... 
} 
+0

Буйте свое литье 'malloc()'! БОО, я говорю! –

+0

Знаешь, я так долго это делал, так что перестала думать об этом. Благодарим вас за то, что он пересматривает эту битку знаний. – retracile

1

О, да, проблема есть.

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

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

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

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

+1

Не называйте их ссылками, вы просто смутитесь, когда погрузитесь в C++. C имеет _pointers_. Массивы распадаются на _pointers_. –

+0

Правильно, вы исправлены! –

0

код, скорее всего, 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); 
} 
0

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

Тем не менее по-прежнему необходимо исправить бизнес с помощью char * vs char **. Вокруг этого нет.

2

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

char mybaz[] = "hello:world"; 
+0

«Выглядел?». Я добрался туда в конце концов ;-) –

+0

Вместе мы - Умбер-ум! Никакой вопрос не может противостоять мощному вмешательству толстяка ... что это было, intneglect? Ну, что бы это ни называлось. –

+0

@Steve: LOL! Я имел в виду это как ладонь. Было несколько ошибок, поскольку все отметили :) – ThePosey

0

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

Решение объявить аргументы как ponter на указатель на 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:

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

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

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