2016-09-01 2 views
1

Я изучаю C, и я играю со структурами, но я нашел какое-то поведение, которое я не могу объяснить, и я хотел бы знать, почему это происходит.Массив структур внутри цикла

Это мой код:

struct my_struct{ 
    char *name; 
}; 

int main() 
{ 

    struct my_struct arr[3]; 
    int i = 0; 
    char str[10]; 

    while (i<3) 
    { 
    fgets(str, 10, stdin); 
    arr[i].name = str; 
    printf("Array number %d: %s", i, arr[i].name); 
    i++; 
    } 

    printf("1 - %s\n2 - %s\n3 - %s", arr[0].name, arr[1].name, arr[2].name); 

    return 0; 
} 

Мой вход:

test1 
test2 
test3 

Ожидаемый результат:

Array number 0: test1 

Array number 1: test2 

Array number 2: test3 
1 - test1 

2 - test2 

3 - test3 

Результирующий выход:

Array number 0: test1 

Array number 1: test2 

Array number 2: test3 
1 - test3 

2 - test3 

3 - test3 

Результат:

Проблема в том, что цикл while продолжает работать, кажется, все в порядке; однако, когда он выходит, он, кажется, устанавливает каждое из значений «name» структур в массиве последним.

Если один из циклов и до последнего printf(), я устанавливаю имя последней структуры в массиве вручную, это единственное, которое обновляется, но имена предыдущих структур все еще заданы до последнего, введенного внутри цикла.

Я предполагаю, что мне не хватает информации об управлении памятью, как очистка буфера, прежде чем звонить в fgets() снова или STH, но не может понять, что происходит. Кто-нибудь знает, что это значит?

ответ

5

Это то, что вы ожидаете, подумайте об этом таким образом, у вас есть char str[10], это память, в которой хранится ваша строка. Когда вы устанавливаете каждое из имен массивов с arr[i].name = str, вы указываете char * name в этой памяти. Так вот то, что ваш цикл делает:

0. str = [];   arr[0].name = NULL; arr[1].name = NULL; arr[1].name = NULL; 
1. str = [string1]; arr[0].name = str; arr[1].name = NULL; arr[1].name = NULL; 
2. str = [string2]; arr[0].name = str; arr[1].name = str; arr[1].name = NULL; 
3. str = [string3]; arr[0].name = str; arr[1].name = str; arr[1].name = str; 

Таким образом, к концу цикла, все arr.name указатели указывают на строку, и вы редактировали строковое каждый раз. Если вы хотите, чтобы отдельные элементы arr хранить свои собственные строки, то вам лучше сделать это:

struct my_struct{ 
    char name[10]; // Note how each instance of `my_struct` now stores its own string. 
}; 

int main() { 
    struct my_struct arr[3]; 
    int i = 0; 

    while (i<3) { 
    fgets(arr[i].name, 10, stdin); 
    printf("Array number %d: %s", i, arr[i].name); 
    i++; 
    } 

    printf("1 - %s\n2 - %s\n3 - %s", arr[0].name, arr[1].name, arr[2].name); 

    return 0; 
} 

Live example

В качестве последнего замечания следует избегать использования fgets(see here). Предпочитают вместо этого getline.

+0

Это было ясно, черт возьми, спасибо много[email protected] – Nico

+0

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

+0

Теперь все готово! @Ben – Nico

2

Вы не можете скопировать строку C так, как это arr[i].name = str.

Что вы делаете, это установка каждого указателя name по тому же адресу, представленного str. Итак, когда вы звоните printf, каждый name указывает на ту же строку, str и printf просто печатает str три раза.

Если вы хотите скопировать строку, используйте strcpy. Кроме того, вам необходимо выделить память для ваших name переменных.

1

Вы должны выделить память для полукокса * имя каждого из структуры:

while (i<3) 
    { 
    fgets(str, 10, stdin); 
    arr[i].name=(char *)malloc(10*sizeof(char)); 
    strcpy(arr[i].name,str); 

    printf("Array number %d: %s", i, arr[i].name); 
    i++; 
    } 

Обратите внимание, что, таким образом, когда вы закончите, используя массив [я], вы должны освободить (массив [я]. имя), для каждого i < 3, чтобы избежать утечек памяти.

+0

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

+0

@Ben, спасибо! Я думал, что излишне говорить об этом, но я должен быть более конкретным в своем ответе, я повторно отредактировал ответ. – coder

2

Поскольку это C, вам необходимо управлять всей структурой памяти/уничтожением/управлением самостоятельно.

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

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

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

Существует несколько способов отладки программы C, чтобы выяснить это, например, используя gdb на Linux или Visual Studio в Windows, но это самый простой способ показать здесь.

Добавление некоторых отладочный вывод в вашей программе, вы получите это:

#include<stdio.h> 

struct my_struct{ 
    char *name; 
}; 

int main() 
{ 

    struct my_struct arr[3]; 
    int i = 0; 
    char str[10]; 

    while (i<3) 
    { 
    fgets(str, 10, stdin); 
    arr[i].name = str; 
    printf("Array number %d: %s", i, arr[i].name); 
    printf(" %p\n", arr[i].name); 
    i++; 
    } 

    printf("\n1 - %s (%p)\n2 - %s (%p)\n3 - %s (%p)", 
    arr[0].name, arr[0].name, 
    arr[1].name, arr[1].name, 
    arr[2].name, arr[2].name); 

    return 0; 
} 

Это приводит следующий вывод (с учетом john, jacob и jingle в качестве входных данных):

Array number 0: john 
    0xfff8d96a 
Array number 1: jacob 
    0xfff8d96a 
Array number 2: jingle 
    0xfff8d96a 

1 - jingle 
(0xfff8d96a) 
2 - jingle 
(0xfff8d96a) 
3 - jingle 
(0xfff8d96a) 

Из этого , мы можем видеть, что вы явно переписываете один и тот же адрес памяти каждый раз. Причина этого заключается в том, что name присвоен str, а более педантично name устанавливается в том месте, где в памяти находится char*str, что, вероятно, не изменится. Это по существу то же самое, что и x = 3 в цикле три раза.

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

  1. Выделите каждый экземпляр arr[i].name, прежде чем использовать его. Этого можно достичь, используя malloc или calloc от stdlib.h.
  2. Скопируйте исходный код, полученный от stdin в arr[i].name. Это может быть достигнуто с помощью strcpy или strncpy (предпочтительно) от string.h

После применения исправления (две строки кода), ваш цикл будет выглядеть следующим образом:

while (i<3) 
{ 
    fgets(str, 10, stdin); 

    // Allocate 10 (most likely) bytes of memory to arr[i].name 
    // And also clear out that memory space  
    arr[i].name = (char*)calloc(10, sizeof(char)); 

    // Safely copy the data (max 10 chars) from 'str' into 'arr[i].name' 
    strncpy(arr[i].name, str, 10); 

    printf("Array number %d: %s", i, arr[i].name); 
    printf(" %p\n", arr[i].name); 

    i++; 
} 

После применения исправления, то конечный результат:

Array number 0: john 
    0x89a0008 
Array number 1: jacob 
    0x89a0018 
Array number 2: jingle 
    0x89a0028 

1 - john 
(0x89a0008) 
2 - jacob 
(0x89a0018) 
3 - jingle 
(0x89a0028) 
+0

Мои сомнения были решены, прежде чем вы ответили, поэтому я уже принял это. Твой довольно ясный и полезный. большое спасибо!! +1 – Nico

+0

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

+0

@Ben, вы правы, я случайно скопировал правильный ответ дважды. Благодаря! – homersimpson

Смежные вопросы