Возврат строки C из функции

109

Я пытаюсь вернуть строку C из функции, но она не работает. Вот мой код.

char myFunction()
{
    return "My String";
}

В mainЯ зову это следующим образом :

int main()
{
  printf("%s", myFunction());
}

Я также пробовал другие способы myFunction, но они не работают. Например:

char myFunction()
{
  char array[] = "my string";
  return array;
}

Примечание: мне не разрешено использовать указатели!

Небольшая справка по этой проблеме:

Есть функция, которая определяет, какой сейчас месяц. Например, если он равен 1, он возвращает январь и т. Д.

Поэтому , когда он собирается печатать, он делает это так: printf("Month: %s",calculateMonth(month));. Теперь проблема в том, как вернуть эту строку из calculateMonthфункции.

itsaboutcode
источник
10
К сожалению, в этом случае вам понадобятся указатели.
Ник Бедфорд,
1
@Hayato Ну, я считаю, что мы взрослые здесь и знаем, что он должен возвращать 0, это было просто для того, чтобы привести пример lox ..
itsaboutcode
3
return 0по умолчанию подразумевается только в C99 (и C ++), но не в C90.
hrnt
1
Тогда вы не сможете этого сделать, кроме глупых хаков, которые в любом случае на самом деле просто манипуляции с сломанными указателями. Указатели существуют не просто так ...: |
GManNickG

Ответы:

223

Ваша подпись функции должна быть:

const char * myFunction()
{
    return "My String";
}

Задний план:

Это фундаментально для C и C ++, но мы не будем останавливаться на достигнутом.

В C (и C ++, если на то пошло) строка - это просто массив байтов, оканчивающийся нулевым байтом, поэтому термин «строка-ноль» используется для представления этого конкретного вида строки. Существуют и другие типы строк, но в C (и C ++) эта разновидность по сути понимается самим языком. Другие языки (Java, Pascal и т. Д.) Используют разные методологии для понимания «моей строки».

Если вы когда-либо использовали Windows API (который находится на C ++), вы довольно часто будете видеть параметры функции, такие как: «LPCSTR lpszName». Часть 'sz' представляет понятие 'нулевая строка': массив байтов с нулевым (/ нулевым) ограничителем.

Уточнение:

Ради этого «вступления» я использую слова «байты» и «символы» как синонимы, потому что так легче выучить. Имейте в виду, что существуют другие методы ( расширенные символы и многобайтовые системы символов ( mbcs )), которые используются для работы с международными символами. UTF-8 представляет собой пример MBCS. Ради вступления я все это незаметно «пропускаю».

Объем памяти:

Это означает, что строка типа «моя строка» фактически использует 9 + 1 (= 10!) Байтов. Это важно знать, когда вы наконец дойдете до динамического распределения строк.

Итак, без этого «завершающего нуля» у вас не будет строки. В памяти находится массив символов (также называемый буфером).

Долговечность данных:

Использование функции таким образом:

const char * myFunction()
{
    return "My String";
}

int main()
{
    const char* szSomeString = myFunction(); // Fraught with problems
    printf("%s", szSomeString);
}

... как правило, приводит к случайным необработанным исключениям / ошибкам сегментов и т.п., особенно «в будущем».

Короче говоря, хотя мой ответ правильный - в 9 случаях из 10 вы получите программу, которая дает сбой, если вы используете ее таким образом, особенно если вы считаете, что это «хорошая практика». Вкратце: обычно это не так.

Например, представьте, что когда-нибудь в будущем строку нужно будет каким-то образом изменить. Как правило, программист выбирает легкий путь и (пытается) писать такой код:

const char * myFunction(const char* name)
{
    char szBuffer[255];
    snprintf(szBuffer, sizeof(szBuffer), "Hi %s", name);
    return szBuffer;
}

То есть ваша программа выйдет из строя, потому что компилятор (может / не мог) освободил память, используемую к szBufferмоменту вызова printf()in main(). (Ваш компилятор также должен заранее предупредить вас о таких проблемах.)

Есть два способа вернуть строки, которые не так быстро прерываются.

  1. возвращающие буферы (статические или динамически выделенные), которые живут некоторое время. В C ++ используйте вспомогательные классы (например, std::string) для обработки долговечности данных (что требует изменения возвращаемого значения функции), или
  2. передать буфер функции, которая заполняется информацией.

Обратите внимание, что невозможно использовать строки без указателей в C. Как я показал, они синонимы. Даже в C ++ с шаблонными классами всегда есть буферы (то есть указатели), используемые в фоновом режиме.

Итак, чтобы лучше ответить на (теперь измененный вопрос). (Наверняка будет множество «других ответов», которые могут быть предоставлены.)

Более безопасные ответы:

Пример 1 с использованием статически выделенных строк:

const char* calculateMonth(int month)
{
    static char* months[] = {"Jan", "Feb", "Mar" .... };
    static char badFood[] = "Unknown";
    if (month<1 || month>12)
        return badFood; // Choose whatever is appropriate for bad input. Crashing is never appropriate however.
    else
        return months[month-1];
}

int main()
{
    printf("%s", calculateMonth(2)); // Prints "Feb"
}

Что делает здесь «статика» (многим программистам не нравится этот тип «распределения»), так это то, что строки помещаются в сегмент данных программы. То есть он размещен постоянно.

Если вы перейдете на C ++, вы будете использовать аналогичные стратегии:

class Foo
{
    char _someData[12];
public:
    const char* someFunction() const
    { // The final 'const' is to let the compiler know that nothing is changed in the class when this function is called.
        return _someData;
    }
}

... но, вероятно, проще использовать вспомогательные классы, например std::string, если вы пишете код для собственного использования (а не как часть библиотеки для совместного использования с другими).

Пример 2 с использованием буферов, определяемых вызывающим абонентом:

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

void calculateMonth(int month, char* pszMonth, int buffersize)
{
    const char* months[] = {"Jan", "Feb", "Mar" .... }; // Allocated dynamically during the function call. (Can be inefficient with a bad compiler)
    if (!pszMonth || buffersize<1)
        return; // Bad input. Let junk deal with junk data.
    if (month<1 || month>12)
    {
        *pszMonth = '\0'; // Return an 'empty' string
        // OR: strncpy(pszMonth, "Bad Month", buffersize-1);
    }
    else
    {
        strncpy(pszMonth, months[month-1], buffersize-1);
    }
    pszMonth[buffersize-1] = '\0'; // Ensure a valid terminating zero! Many people forget this!
}

int main()
{
    char month[16]; // 16 bytes allocated here on the stack.
    calculateMonth(3, month, sizeof(month));
    printf("%s", month); // Prints "Mar"
}

Есть много причин, по которым второй метод лучше, особенно если вы пишете библиотеку для использования другими (вам не нужно блокировать конкретную схему распределения / освобождения, третьи стороны не могут взломать ваш код, и вам не нужно связываться с конкретной библиотекой управления памятью), но, как и весь код, вам решать, что вам больше всего нравится. По этой причине большинство людей выбирают пример 1, пока их не сожгут столько раз, что они отказываются писать его таким образом;)

Отказ от ответственности:

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

cmroanirgo
источник
2
Фактически, функция должна возвращать a char *, поскольку строковые литералы в C имеют тип char[]. Однако их нельзя изменять каким-либо образом, поэтому const char*предпочтение отдается возврату (см. Securecoding.cert.org/confluence/x/mwAV ). Возврат char *может потребоваться, если строка будет использоваться в устаревшей или внешней библиотечной функции, которая (к сожалению) ожидает char*аргумент as, даже если она будет только читать из него. C ++, с другой стороны, имеет строковые литералы const char[]типа (и, начиная с C ++ 11, вы также можете иметь std::stringлитералы).
TManhente
17
@cmroanirgo - префикс my объявляет читателю, что функция была создана пользователем. Я считаю вполне разумным использовать в таком контексте.
Quant
4
согласно здесь: stackoverflow.com/questions/9970295/… , вы можете вернуть строковый литерал
giorgim
6
Код, помеченный fraught with problemsв разделе «Долговечность данных», на самом деле совершенно действителен. Строковые литералы имеют статическое время жизни в C / C ++. См. Ссылку, упомянутую Георгием выше.
chengiz
1
@cmroanirgo Возвращение строковых литералов - хорошая практика и хороший стиль. Он не «чреват проблемами», и он не выйдет из строя 9 раз из 10: он никогда не выйдет из строя. Даже компиляторы 80-х годов (по крайней мере, те, которые я использовал) правильно поддерживают неограниченное время жизни строковых литералов. Примечание. Я не совсем понимаю, что вы имели в виду при редактировании ответа: я до сих пор вижу, что в нем говорится, что он подвержен сбоям.
cesss
12

Строка AC определяется как указатель на массив символов.

Если у вас не может быть указателей, по определению у вас не может быть строк.

Crashworks
источник
Вы можете передать массив в функцию , а затем работать на этом массиве: void foo( char array[], int length). Конечно, arrayэто указатель под капотом, но он не является «явным» указателем, и поэтому он может быть более интуитивно понятным для тех, кто изучает массивы, но не совсем изучил указатели.
jvriesem
12

Обратите внимание на эту новую функцию:

const char* myFunction()
{
    static char array[] = "my string";
    return array;
}

Я определил «массив» как статический. В противном случае, когда функция завершается, переменная (и возвращаемый указатель) выходит за пределы области видимости. Поскольку эта память выделена в стеке, она будет повреждена. Обратной стороной этой реализации является то, что код не является реентерабельным и потокобезопасным.

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

Как отмечено в комментарии, это очень плохая практика, поскольку злоумышленник может затем внедрить код в ваше приложение (ему / ей нужно открыть код с помощью GDB, затем создать точку останова и изменить значение возвращаемой переменной на переполнение и веселье только начинается).

Гораздо более рекомендуется позволить вызывающему абоненту обрабатывать распределение памяти. См. Этот новый пример:

char* myFunction(char* output_str, size_t max_len)
{
   const char *str = "my string";
   size_t l = strlen(str);
   if (l+1 > max_len) {
      return NULL;
   }
   strcpy(str, str, l);
   return input;
}

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

Elcuco
источник
2
Как правило, это плохой подход. Символом * можно управлять с помощью окружающего кода. То есть вы можете делать что-то вроде этого: strcpy (myFunction (), «Очень длинная строка»); и ваша программа выйдет из строя из-за нарушения прав доступа.
cmroanirgo
Чего-то не хватает рядом с «тем, что пользователь» .
Питер Мортенсен
8

Ваша проблема связана с типом возвращаемого значения функции - он должен быть:

char *myFunction()

... и тогда ваша первоначальная формулировка будет работать.

Обратите внимание, что у вас не может быть строк C без задействованных указателей где-то вдоль линии.

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

кафе
источник
1
Я думаю, что подпись должна быть const char *, поскольку строка является литералом, но, если я не ошибаюсь, компилятор примет это.
Люк,
5

Основываясь на вашей недавно добавленной предыстории с вопросом, почему бы просто не вернуть целое число от 1 до 12 для месяца и позволить функции main () использовать оператор switch или лестницу if-else, чтобы решить, что печатать? Это, конечно, не лучший способ - char * был бы - но в контексте такого класса, как я думаю, он, вероятно, самый элегантный.

Twisol
источник
3

Вы можете создать массив в вызывающей стороне, которая является основной функцией, и передать массив вызываемой стороне, которая является вашей myFunction (). Таким образом myFunction может заполнить строку в массиве. Однако вам нужно объявить myFunction () как

char* myFunction(char * buf, int buf_len){
  strncpy(buf, "my string", buf_len);
  return buf;
}

А в основной функции myFunction следует вызывать так:

char array[51];
memset(array, 0, 51); /* All bytes are set to '\0' */
printf("%s", myFunction(array, 50)); /* The buf_len argument  is 50, not 51. This is to make sure the string in buf is always null-terminated (array[50] is always '\0') */

Однако указатель все еще используется.

ChainLooper
источник
2

Тип возврата вашей функции - один символ ( char). Вы должны вернуть указатель на первый элемент массива символов. Если вы не можете использовать указатели, значит, вы облажались. :(

hrnt
источник
2

Или как насчет этого:

void print_month(int month)
{
    switch (month)
    {
        case 0:
            printf("January");
            break;
        case 1:
            printf("february");
            break;
        ...etc...
    }
}

И назовите это месяцем, который вы вычислили в другом месте.

Себастьян М
источник
1
+1 не то, что просил OP, но, вероятно, это то, что ожидает от вас задание, поскольку он не может использовать указатели.
Vitim.us
Даже printf использует указатели. Указатель похож на нож - он необходим для жизни и работы, но вы должны держать его за ручку и использовать острую сторону, чтобы резать, иначе вам будет плохо. Неудачное размещение пробелов в определении функции - ошибка мозга многих начинающих программистов на C. char * func (символ * s); char func (char * s); char func * char * s); все одинаковы, но все выглядят по-разному, и, что еще больше неясно, * также является оператором отмены ссылки для переменных, которые являются указателями.
Крис Рид,
1

A char- это только один однобайтовый символ. Он не может хранить строку символов и не является указателем (которого у вас, по-видимому, не может быть). Следовательно, вы не можете решить свою проблему без использования указателей (что char[]является синтаксическим сахаром для).

Ник Бедфорд
источник
1

Если вы действительно не можете использовать указатели, сделайте что-нибудь вроде этого:

char get_string_char(int index)
{
    static char array[] = "my string";
    return array[index];
}

int main()
{
    for (int i = 0; i < 9; ++i)
        printf("%c", get_string_char(i));
    printf("\n");
    return 0;
}

Магическое число 9 ужасно, и это не пример хорошего программирования. Но вы поняли. Обратите внимание, что указатели и массивы - это одно и то же (своего рода), так что это немного обман.

Себастьян М
источник
Обычно, если вам нужно реализовать такие решения домашних заданий, ваши предварительные предположения ошибочны.
hrnt
1

Что ж, в вашем коде вы пытаетесь вернуть String(в C это не что иное, как массив символов с завершающим нулем), но тип возврата вашей функции - это то, charчто вызывает у вас все проблемы. Вместо этого вы должны написать это так:

const char* myFunction()
{

    return "My String";

}

И всегда полезно квалифицировать свой тип constпри назначении литералов в C указателям, поскольку литералы в C не подлежат изменению.

Чешар
источник
0

В вашем прототипе функции указано, что ваша функция вернет символ. Таким образом, вы не можете вернуть строку в своей функции.

user224579
источник
0
char* myFunction()
{
    return "My String";
}

В C строковые литералы представляют собой массивы с классом статической константной памяти, поэтому возвращение указателя на этот массив безопасно. Более подробная информация содержится в вопросе о переполнении стека «Время жизни» строкового литерала в C

Олег Караван
источник
0

Строка возврата из функции

#include <stdio.h>

const char* greet() {
  return "Hello";
}

int main(void) {
  printf("%s", greet());
}
страстный
источник