Ryan Thomsen Ответов: 2

Борьба с использованием malloc и memcpy для динамического выделения mem


Честно говоря, я работаю над заданием уже несколько лет.
около 10 часов и я ни за что на свете не смогу понять динамическую память
Распределение. Этот код должен запрашивать у пользователя название, вес и количество калорий пищи. Затем предполагается, что имя будет сохранено во временном буфере символов. Далее следует точное количество места, необходимое для представления названия продукта с его нулевым Терминатором. После этого я должен динамически выделить этот точный объем памяти и сохранить указатель на него в имени элемента структуры питания. Наконец, я копирую название еды, используя функцию memcpy. Мне также сказали использовать malloc вместо calloc и realloc. Когда я запускаю код, я получаю ошибки доступа к памяти, скорее всего, потому, что я не верю, что правильно распределяю ресурсы.
Код:
#define LUNCH_QTY 3
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
   struct Food
   {
      char* name; int weight, calories;
   } lunches[LUNCH_QTY] = {{(char*)"apple", 4, 100}, {(char*)"salad", 2, 80}};

   for (int element = 2; element < LUNCH_QTY; element++)
   {
      char name[100];
      printf("Please enter, space-separated, the name, weight and calories of a food: ");
      scanf("%s %d %d", &name, &lunches[element].weight, &lunches[element].calories);
      if ((lunches[element].name = (char*)malloc(strlen(name))) == NULL)
      {
         fputs("Unable to allocate memory\n", stderr);
         exit(1);
      }
      memcpy(lunches[element].name, &name, strlen(name));
      printf("%s %d %d", lunches[element].name, lunches[element].weight, lunches[element].weight);
      free(name);
      name = NULL;
   }
}
Любая помощь очень ценится

Что я уже пробовал:

Я пробовал использовать Malloc как для имени, так и для имени члена пищевой структуры. Я также попытался добавить & примерно в каждом месте, которое я могу придумать, и удалить его также.

2 Ответов

Рейтинг:
0

Jon McKee

malloc выделяет блок неинициализированной непрерывной памяти запрошенного размера в байтах и передает обратно void* указатель, так как он понятия не имеет, для чего вам нужна эта память.

Итак, как мы вычисляем, сколько байтов нам нужно тогда? Ну, в данном случае у нас есть массив символов, поэтому нам нужно пространство для каждого элемента (char) и общий размер строки. Чтобы получить размер элемента, мы можем использовать sizeof(char) Чтобы получить общий размер массива, мы можем использовать strlen(name) + 1.

Это ваш код:

(char*)malloc(strlen(name)) //If name is "apple", strlen will return 5 (it doesn't include the termination character '\0'). See: http://www.cplusplus.com/reference/cstring/strlen/


Вы видите проблему? Если вы поставите точку останова на printf постройте и посмотрите на структуру lunches[2], вы увидите имя, за которым следует случайный мусор, потому что там нет символа нулевого завершения. Вот почему нам нужно, чтобы +1 на обоих malloc и memcpy чтобы выделить место для Терминатора строки и скопировать его, соответственно.

Кроме того, вам повезло, что размер char это один байт, в противном случае ваш malloc не будет выделять правильный размер. Вообще говоря, вы хотите, чтобы у вас вошло в привычку делать это так, как malloc(sizeof(char) * (strlen(name) + 1)). Это приведет к меньшим головным болям в будущем (а char это не всегда 1 байт). Однако "+1" не понадобится, например, с целочисленным массивом - malloc(sizeof(int) * arraySize).

Однако это не имеет никакого отношения к вашей проблеме доступа к памяти. Это вызвано тем, что
free(name);
name = NULL;

Только ты free что вы malloc. name распределяется по стеку. Он будет отброшен, когда стековый фрейм будет выскочен при выходе функции. Это также, почему в строке name = NULL; должен показать ошибку в вашем компиляторе. Вы не можете повторно назначить name. Это не указатель на массив символов, это массив символов- Ты это нарочно сделал free название структуры, но случайно ссылается на локальное имя вместо этого?


Ryan Thomsen

Большое вам спасибо за ваш ответ, он помог прояснить довольно много вещей на самом деле. Я чувствую, что следил за большинством ваших изменений, однако я изо всех сил пытаюсь выяснить некоторые ошибки. Я получаю предупреждение C6385,C6386 и 6054. Когда я запускаю программу, она никогда не проходит мимо ввода входных данных с помощью scanf. Может быть, я что-то сделал не так?

 #define LUNCH_QTY 3
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
   struct Food
   {
      char* name; int weight, calories;
   } lunches[LUNCH_QTY] = {{(char*)"apple", 4, 100}, {(char*)"salad", 2, 80}};

   for (int element = 2; element <= (LUNCH_QTY); element++)
   {
      char name[100];
      printf("Please enter, space-separated, the name, weight and calories of a food: ");
      if (scanf("%99s %d %d", name, &lunches[element].weight, &lunches[element].calories) == 1)
      {
         lunches[element].name = (char*)malloc(strlen(name)+1);
         if (lunches[element].name)
         {
            memcpy(lunches[element].name, &name, strlen(name)+1);
         }
         else
         {
            fputs("Unable to allocate memory\n", stderr);
            exit(1);
         }
      }
      else
      {
         fputs("Unable to read input\n", stderr);
         exit(1);
      }

      printf("%s %d %d", lunches[element].name, lunches[element].weight, lunches[element].weight);
      free(lunches[element].name);
      lunches[element].name = NULL;
   }
} 

Jon McKee

Это очень много измененного кода.
- Подумай об этом. for петля тщательно, конкретно <=. Вы создаете 3 ланча (lunches[LUNCH_QTY]), но массивы индексируются нулем, так что общее количество обедов-это ланчи[3]?


Nvm, вы можете использовать только имя. scanf не имеет никаких проблем с простым использованием имени, как работает Пример Рика. Прошла минута с тех пор, как я пользовался хорошим Оле Си. Ваш оригинальный пример с моими изменениями работает нормально, и пример Рика ниже с моими заметками работает нормально, так что у вас есть два примера для работы :)

Рейтинг:
0

Rick York

Джон прямо на носу. Ваш цикл for должен быть переписан так, чтобы он выглядел примерно так :

struct Food
{
   char* name;
   int weight;
   int calories;
};
struct Food lunches[ LUNCH_QTY ];
struct Food * pfood;
char name[ 100 ] = { 0 };
int memorysize;
for (int element = 0; element < LUNCH_QTY; element++)
{
   pfood = & lunches[ element ];
   printf( "Please enter, space-separated, the name, weight, and calories of a food: ");
   scanf( "%s %d %d", name, & pfood->weight, & pfood->calories );
   memorysize = strlen( name ) + 1;   // include the null
   pfood->name = (char*)malloc( memorysize );
   if( pfood->name == NULL )
   {
      fprintf( stderr, "Unable to allocate memory\n" );
      exit(1);
   }
   memcpy( pfood->name, name, memorysize );
}

for( int element = 0; element < LUNCH_QTY; element++ )
{
   pfood = & lunches[ element ];
   fprintf( stdout, "%d : %s %d %d\n",
       element + 1, pfood->name, pfood->weight, pfood->calories );

   free( pfood->name );
   pfood->name = NULL;
}

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

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


Jon McKee

+1: Мне нравится, что вы указываете лучшую стратегию для инициализации вещей.

Однако это с, так что вам нужно struct Food lunches[LUNCH_QTY] и struct Food * pfood Кроме того, в scanf это должно быть &pfood->weight и &pfood->calories С этими изменениями он компилируется и работает нормально :)

Rick York

Спасибо. Я давно не имел дела с Си и всегда забываю о его различиях с Си++.

Jon McKee

То же. Мне потребовалась минута, чтобы понять, почему мой IDE говорил, что еда-это не тип, лол.

Rick York

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