Поскольку я не могу вернуть локальную переменную, каков наилучший способ вернуть строку из функции C или C ++? - PullRequest
18 голосов
/ 08 января 2009

Как продолжение этот вопрос :

Из того, что я видел, это должно работать как ожидалось:

void greet(){
  char c[] = "Hello";
  greetWith(c);
  return;
}

но это приведет к неопределенному поведению:

char *greet(){ 
  char c[] = "Hello";
  return c;
}

Если я прав, как лучше исправить вторую функцию приветствия? Во встроенной среде? На рабочем столе?

Ответы [ 8 ]

33 голосов
/ 08 января 2009

Ты абсолютно прав. Ваш массив c во втором примере размещается в стеке, и, следовательно, память будет сразу же использоваться повторно. В частности, если у вас был код типа

 printf("%s\n",greet());

вы получите странные результаты, потому что вызов printf использовал бы часть пространства вашего массива.

Решение состоит в том, чтобы выделить память где-то еще. Для примера:

char c[] = "Hello";

char * greet() {
    return c;
}

Будет работать. Другой вариант - статически распределить его по объему:

char * greet() {
    static char c[] = "Hello";
    return c;
}

потому что статическая память выделяется отдельно от стека в пространстве данных.

Ваш третий выбор - разместить его в куче через malloc:

char * greet() {
   char * c = (char *) malloc(strlen("Hello")+1);  /* +1 for the null */
   strcpy(c, "Hello");
   return c;
}

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

Обновление

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

{
    /* stuff happens */
    printf("%s\n", greet());
}

Это утечка; возвращается указатель на память malloc, printf использует его, а затем теряется; Вы не можете освободить это больше. С другой стороны,

{
    char * cp ;
    /* stuff happens */
    cp = greet();
    printf("%s\n", cp);
    free(cp);
}

не утечка, потому что указатель сохраняется в автоматической переменной cp достаточно долго, чтобы вызвать free() на нем. Теперь, несмотря на то, что cp исчезает, как только выполнение завершается, он завершает фигурную скобку, так как free был вызван, память восстанавливается и не протекает.

12 голосов
/ 08 января 2009

Если вы используете C ++, вы можете рассмотреть возможность использования std::string для возврата строк из вашей второй функции:

std::string greet() {
    char c[] = "Hello";
    return std::string(c); // note the use of the constructor call is redundant here
}

Или в однопоточной среде вы можете сделать:

char *greet() {
    static char c[] = "Hello";
    return c;
}

Здесь static выделяет пространство в области глобальной памяти, которое никогда не исчезает. Однако этот static метод чреват опасностью.

10 голосов
/ 08 января 2009

Зависит от того, есть ли у встроенной среды куча или нет, если да, то вы должны malloc следующим образом:

char* greet()
{
  char* ret = malloc(6 * sizeof(char)); // technically " * sizeof(char)" isn't needed since it is 1 by definition
  strcpy(ret,"hello");
  return ret;
}

Обратите внимание, что позже вам следует вызвать free () для очистки. Если у вас нет доступа к динамическому распределению, вам нужно будет сделать его глобальным или переменной в стеке, но дальше в стеке вызовов.

7 голосов
/ 08 января 2009

Если вам нужно сделать это в C ++, использование std::string, как предложено Грегом Хьюгиллом, определенно является самой простой и поддерживаемой стратегией.

Если вы используете C, вы можете рассмотреть возможность возврата указателя на пространство, которое было динамически выделено с помощью malloc(), как предложено Джесси Пеппером; но другой способ избежать динамического размещения - это greet() принять параметр char * и записать его вывод там:

void greet(char *buf, int size) {
    char c[] = "Hello";

    if (strlen(c) + 1 > size) {
        printf("Buffer size too small!");
        exit(1);
    }

    strcpy(buf, c);
}

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

5 голосов
/ 08 января 2009

Вы можете использовать любой из них:

char const* getIt() {
    return "hello";
}

char * getIt() {
    static char thing[] = "hello";
    return thing;
}

char * getIt() {
    char str[] = "hello";
    char * thing = new char[sizeof str];
    std::strcpy(thing, str);
    return thing;
}

shared_array<char> getIt() {
    char str[] = "hello";
    shared_array<char> thing(new char[sizeof str]);
    std::strcpy(thing.get(), str);
    return thing;
}

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

4 голосов
/ 08 января 2009

Возвращение памяти malloc из функции, как предложено в нескольких других ответах, просто требует утечки памяти. Звонящий должен знать, что вы malloc'd его, а затем позвонить бесплатно. Если они случайно вызвали команду delete, результаты не определены (хотя, вероятно, все в порядке).

Было бы лучше заставить вызывающую сторону предоставить память для вас, а затем скопировать в нее вашу строку. Таким образом, вызывающий абонент получает уведомление о том, что ему нужно убраться.

1 голос
/ 08 января 2009

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

char *greet(char *buf, int size) {
     char *str = "Hello!"
     if (strlen(str) + 1 > size) { // Remember the terminal null!
          return NULL;
     } 
     strcpy(buf, str);
     return buf;
}

void do_greet() {
    char buf[SIZE];
    if (greet(buf, SIZE) == NULL) {
        printf("Stupid C");
     } 
     else {} // Greeted!
}

Тонна работы для простой задачи ... но есть C для вас :-) К сожалению! Думаю, меня избил random_hacker ...

0 голосов
/ 08 января 2009

Выделить массив символов в куче?

Возможность использования malloc или нет, зависит только от того, что вы подразумеваете под «встроенной средой».

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