Доступ к массивам в указателе на структуру

#c #pointers #c99 #struct #dereference

#c #указатели #c99 #структура #разыменование

Вопрос:

У меня есть простая структура:

 typedef struct {
    void *things;
    int sizeOfThings;
} Demo;
  

things предназначен для хранения массива отдельных «вещей», таких как, возможно, строки или целые числа.
Я создаю указатель на него:

 Demo * Create(int value) {
    Demo *d = malloc(sizeof(Demo));
    if (d != NULL) {
        d->sizeOfThings = value;
        d->things = malloc(20 * value); // We'll have a max of 20 things
    }
}
  

value является sizeof(int) для массива целых чисел, например.

Если в другой функции я хочу вставить что-то в d-> things (предполагая, по крайней мере, не то, что я просто добавляю это в первый слот, управление положением выполняется в другом месте):

 char * thing = "Me!";
strncpy(d->things[0], amp;thing, d->sizeOfThings);
  

Я обхожу область strncpy

 test.c:10: warning: pointer of type ‘void *’ used in arithmetic
test.c:10: warning: dereferencing ‘void *’ pointer
test.c:10: error: invalid use of void expression
  

Я просто пытаюсь понять использование void * как способ обобщения моих функций. Я подозреваю, что что-то не так с d->things[0] .

Ответ №1:

Согласно стандарту C, void не имеет размера — sizeof(void) не определен. (Некоторые реализации делают его sizeof(int), но это несовместимо.)

Когда у вас есть массив типа foo, это выражение:

 array[3]
  

Добавляет 3*sizeof(foo) к адресу, хранящемуся в массиве, а затем откладывает его. Это потому, что все значения упакованы вместе в памяти. Поскольку sizeof(void) не определен, вы не можете сделать это для массивов void (фактически, вы даже не можете иметь массивы void, только указатели void.)

Вы должны привести любой указатель void к другому типу указателя, прежде чем рассматривать его как массив:

 d->things = malloc(20 * sizeof(int));
(int *)(d->things)[0] = 12;
  

Однако имейте в виду, что вам даже не нужно этого делать, чтобы использовать strncpy для этого. Strncpy может просто отлично принимать указатель void. Но вы неправильно использовали strncpy. Ваш вызов strncpy должен выглядеть следующим образом:

 strncpy(d->things, thing, d->sizeOfThings);
  

Что сделала бы ваша версия, так это попыталась бы обработать первый элемент массива d-> things как указатель, когда это не так, и обработала бы amp;thing , который является символом ** , как если бы это был просто символ *.

Комментарии:

1. Это полезно. Буду ли я использовать арифметику указателей для будущих элементов strncpy?

2. Вы правы. Но в этом случае это был «sizeof(void *)», а не «sizeof(void)».

3. Я не уверен, что вы имеете в виду. Strncpy скопирует строку (которая является просто массивом символов). Если вы хотите объединить несколько stings вместе в одном большом буфере, вы могли бы использовать strncpy для указателя смещения, но гораздо проще использовать strncat.

4. Ну, в идеале, это массив void *?

5. В этом случае вам следует изменить объявление в вашей структуре на void ** things. И тогда да, вы используете d-> things[0] (в том числе при присвоении результата malloc!)

Ответ №2:

Попробуйте посмотреть, устраняет ли это вашу проблему:

 char *thing = "Me!";
strncpy(amp;d->things[0], thing, d->sizeOfThings);
  

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

 char *thing = "Me!";
strncpy((char *) amp;d->things[0], (const char *) thing, d->sizeOfThings);
  

Ответ №3:

 Demo *d = malloc(sizeof(Demo));
if (d != NULL) {
    d->things = malloc(20 * sizeOfThings); // We'll have a max of 20 things
}
  

Для чего sizeOfThings инициализируется? Вероятно, это может содержать мусор и является причиной ошибки. Даже если по умолчанию он инициализирован в 0, то malloc возвращает NULL( malloc( 20 * 0 ) ; ). Итак, я подозреваю —

 strncpy(d->things[0], amp;thing, d->sizeOfThings);
      // ^^^^^^^^^^ causing the error.
  

Комментарии:

1. @Rio — Предупреждение ясно говорит об этом — test.c:10: warning: dereferencing ‘void *’ pointer

2. sizeOfThings устанавливается в sizeof(char *) для строк и sizeof(int) для целых чисел.

3. @Rio — Вы запрашиваете память, размер которой составляет 20 *sizeOfThings . Но в момент выполнения malloc инструкции, каково sizeOfThings значение? Также, как предложил @shinkou, второй параметр должен thing .

4. Прямо сейчас я работаю с массивом целых чисел. gdb сообщает мне, что размер вещей равен 4.

5. @Rio — Где вы инициализируете sizeOfThings значение 4, которое у вас не указано в той части вопроса, которую вы опубликовали? Я подозреваю, что вы видите, sizeof(sizeOfThings) что в любом случае составляет 4 байта.

Ответ №4:

Две вещи:

Во-первых, определенно что-то не так с использованием d->things[0]. d-> things на самом деле является указателем, и соглашение заключается в том, что указатели и массивы в основном взаимозаменяемы (за несколькими исключениями), и имя массива всегда будет указывать на первый элемент массива.

Во-вторых, функциональной сигнатурой strncpy является char* strncpy(char* назначение, const char* источник, size_t num);. Итак, чтобы это сработало, мы должны преобразовать d-> thing из void * в char * и убедиться, что мы передаем thing как char * (просто вещь), а не как char ** (который является thing amp;).

итак, мы хотим, чтобы это утверждение вместо:

strncpy((char*)d-> вещи, thing, d->Размер вещей);

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