Возвращение строки (массив символов, указатель символа и т. Д. c.) Из функции - PullRequest
0 голосов
/ 07 января 2020

Я пишу программу для ESP8266 на Arduino SDK. Моих C знаний недостаточно для создания профессионального проекта, поэтому сейчас я тренирую себя по C концепциям программирования.

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

Вот мой код:

float val1;
char val2;

void returnMultiple(float *fGross, char *sGross)
{
  *fGross = 50.0;
  char v_str[10];
  dtostrf(*fGross, 5, 2, v_str);
  sprintf(v_str, "%s", v_str);
  sGross = v_str;
}

Какой смысл я пропустил? Значение моего символа null или esp8266 перезапускается?

Ответы [ 4 ]

0 голосов
/ 07 января 2020

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

Существует два способа реализации этого: память для вашей строки выделяется функцией или вызывающей стороной. Простой способ заключается в следующем:

void toString(char *str, int d) {
    sprinf(str, "%d", d);
}

int main(void) {
    char result[12];
    toString(result, 50);
    puts(result, stdout);
    return 0;
}

В этом случае result - это 12-байтовая строка, расположенная в стеке main. 12 байтов достаточно велики, чтобы хранить строковое представление целого числа, так что это безопасно, если вы не уверены, какой размер может иметь результат, тогда следите.

Второй вариант:

void toString(char **str, int d) {
    char *v_str = malloc(12);
    sprintf(v_str, "%d", d);
    *str = v_str;
}

int main(void) {
    char *result;
    toString(&result, 50);
    puts(result, stdout);
    free(result);
    return 0;
}

В этом случае мы притворяемся, что вызывающая сторона не знает, сколько памяти требуется для строки результата, и разрешаем решать функции toString. Он выделяет столько памяти, сколько необходимо для преобразования, а затем возвращает выделенную строку. Вызывающий должен освободить эту память с free. Обратите внимание, что в этой ситуации мы должны передать адрес и результат, поэтому toString запишет указатель на выделенную строку в нашу переменную результата. Подобные двойные указатели могут показаться запутанными некоторым новичкам в C, но концептуально это похоже на то, как вы передаете указатель на переменную с плавающей точкой (float * fGross).

Лично я предпочитаю первая версия, когда это возможно, поскольку при выделении памяти в стеке не требуется управлять памятью кучи с помощью mallo c и free, что является распространенным источником утечек памяти, особенно для начинающих. Конечно, ничто не мешает вам вызывать эту версию toString с выделенной кучей памятью, если вам нужно.

0 голосов
/ 07 января 2020

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

  • Какой тип памяти используется для буферов
  • Кто будет выделять память
  • Кто освободит память

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

void returnMultiple(float *fGross, char *sGross)
{
  if (fGross == NULL || sGross == NULL)
    return;

  *fGross = 50.0;
  dtostrf(*fGross, 5, 2, sGross);
}

void callerfunc(void)
{
    char buf[10];
    float flt;
    returnMultiple(&flt, buf);
    printf("flt: %f; str: %s\n", flt, buf);
}

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

0 голосов
/ 07 января 2020

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

Что вам нужно сделать, это выделить буфер снаружи. Вы можете либо

  • использовать динамическую память c с чем-то вроде char *str = malloc(10 * sizeof(char)); (не забывая освободить ее, как только она больше не нужна).
  • Внешне определить массив chars, как в примере ниже
float val1;
char val2;

void returnMultiple(float *fGross, char *sGross)
{
  *fGross = 50.0;
  char v_str[10];
  dtostrf(*fGross, 5, 2, v_str);

  /* CHANGES HERE! */
  sprintf(sGross, "%s", v_str);
  // No need to assign to sGross the pointer of a local variable 
  // sGross = v_str;
}

int main( void )
{
  char testString[10];
  float testFloat;

  returnMultiple(&testFloat, testString);

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

  return 0;
}

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

Другое решение размещение массива char в функции returnMultiple(), возвращая указатель на массив char. Параметр sGross в этом случае станет переменной char **.

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

0 голосов
/ 07 января 2020

Можно напрямую скопировать в sGross.

dtostrf(*fGross, 5, 2, sGross);

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

void main()
{
    float val1;
    char val2[10];

    returnMultiple(&val1, val2);
}

Конечный результат тогда будет

void returnMultiple(float *fGross, char *sGross)
{
    *fGross = 50.0;
    dtostrf(*fGross, 5, 2, sGross);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...