Я хочу получить i-й элемент void*. Я понимаю, что это тип void, и я должен указать ему определенный тип данных. Идея заключается в том, что он должен работать с разными типами данных. Если я прав в вопросе, как мне это реализовать?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define MAX_STR_LEN 64
typedef struct Vector {
void *data;
size_t element_size;
size_t size;
size_t capacity;
} Vector;
// Allocate vector to initial capacity (block_size elements),
// Set element_size, size (to 0), capacity
void init_vector(Vector *vector, size_t block_size, size_t element_size){
vector->data = (void*) malloc(block_size * element_size); // i am questioning whether this is correct
vector->element_size = element_size;
vector->size = 0;
vector->capacity = block_size * element_size;
}
void resize(Vector *vector, size_t new_size){
void *data2 = (void*) malloc(new_size * vector->element_size);
int i=0;
memmove(data2,vector->data,new_size * vector->element_size);
vector->data = data2;
if (new_size > vector->size){
for(i=vector->size-1;i<new_size;i++){
vector->data[i]=0; // here is the problem
}
}else{
vector->size = new_size;
}
vector->capacity = new_size*vector->element_size;
}
Не связано: может быть, рассмотреть возможность использования связанный список? Доступ к элементу энный связанного списка осуществляется с помощью O(n)
(вместо O(1)
для массива/вектора).
@WhozCraig Вы имеете в виду, что он забыл проверить возвращение malloc NULL
. Эти общие комментарии о «неопределенном поведении» редко проливают свет на вещи...
@BitTickler я исправил ошибку, извините, если это ввело вас в заблуждение
@BitTickler Нет, я имею в виду, посмотрите на исходный пост, к которому я сделал этот комментарий. malloc
не было malloc
при первоначальном размещении.
Ваши векторные функции могут нет [значительно] обращаться к данным массива, используя синтаксис индекса или указателя, например:
vector->data[i]=0;
Это потому, что это указатель void *
, но, что более важно, если element_size
равно (например) 8, означает ли это, что vector->data
указывает на double
или unsigned long long
?
Это может сделать только абонент ваших функций:
Vector *vec = calloc(1,sizeof(*vec));
init_vector(vec,100,sizeof(double));
double *ptr = vec->data;
for (size_t idx = 0; idx < vec->size; ++idx)
ptr[idx] = idx;
Когда вы расширяете массив в resize
, вы можете выполнять только mem*
функции.
Замените петлю for
на:
memset(&vector->data[vector->size * vector->element_size],0,
(new_size - vector->size) * vector->element_size);
Обновлено:
Есть еще несколько проблем. Хотя у вас могу есть capacity
число байт, чаще всего это число элемент (точно так же, как size
).
Когда я создаю такой динамический массив/векторные объекты/функции, я обычно делаю нетresize
инициализацию элементов.
Это потому, что он [на самом деле] не знает, как инициализировать элементы. Для вектора c++
конструктор знает.
Итак, если мы хотим, чтобы resize
[и init_vector
] сделали это, нам нужно предоставить для этого указатель на функцию.
Вот некоторый рефакторинг кода для иллюстрации:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define MAX_STR_LEN 64
typedef struct Vector Vector;
typedef void (*vecinit_p)(Vector *,size_t idx,size_t count);
struct Vector {
void *data; // pointer to array data
size_t element_size; // number of bytes in an array element
size_t capacity; // number of elements allocated
size_t size; // number of elements in use
vecinit_p initfnc; // pointer to init function
};
// vecptr -- get pointer to i'th element
void *
vecptr(Vector *vec,size_t idx)
// idx -- index of desired element
{
void *ptr;
idx *= vec->element_size;
ptr = vec->data;
ptr += idx;
return ptr;
}
// init_data -- initialize data elements
void
init_data(Vector *vec,size_t idx,size_t count)
// idx -- starting index
// count -- number of elements to initialize
{
void *ptr = vecptr(vec,idx + 0);
void *end = vecptr(vec,idx + count);
memset(ptr,0,end - ptr);
}
// Allocate vector to initial capacity (block_size elements),
// Set element_size, size (to 0), capacity
void
init_vector(Vector *vec, size_t block_size, size_t element_size,vecinit_p fnc)
// block_size -- number of elements
// element_size -- number of bytes in a single element
{
size_t new_len = block_size * element_size;
vec->data = calloc(1,new_len);
vec->element_size = element_size;
vec->size = 0;
vec->capacity = block_size;
// provide a "default" constructor
if (fnc == NULL)
fnc = init_data;
vec->initfnc = fnc;
fnc(vec,0,vec->capacity);
}
// resize -- resize the array
void
resize(Vector *vec, size_t new_cap)
// new_cap -- desired new capacity
{
// get byte length
size_t new_len = new_cap * vec->element_size;
void *data2 = malloc(new_len);
if (data2 == NULL) {
perror("malloc");
exit(1);
}
vec->data = data2;
// get old capacity and set new capacity
size_t old_cap = vec->capacity;
vec->capacity = new_cap;
// initialize new elements
if (new_cap > old_cap)
vec->initfnc(vec,old_cap,old_cap - new_cap);
}
// vecpush -- append element to array
// RETURNS: pointer to "pushed" element
void *
vecpush(Vector *vec)
{
// increase array capacity if needed
if (vec->size >= vec->capacity)
resize(vec,vec->capacity + 10);
// point to element
void *ptr = vecptr(vec,vec->size++);
return ptr;
}
C — язык для адреналиновых наркоманов. Это всегда похоже на свободное скалолазание, прыжки с парашютом и гонки Формулы-1. Защитное программирование не является идеальной защитой от всякого рода «саботажа» вызывающего абонента, но это лучше, чем ничего.
Код ниже как раз в этом духе. Он не может обнаружить все возможные проблемы (например, поврежденную память или если указатель данных в векторе является просто случайным значением), но демонстрирует наименьшее количество защитного программирования, которое следует использовать при написании на этом языке.
C имеет «арифметику указателя» как функцию. Итак, если у вас есть, например. a uint16_t * p = 1000;
и вы получаете доступ к p + 1
, он обращается к 1002
(т.е. p + sizeof(uint16_t) * 1
).
И это трюк, как вы можете получить доступ к элементу в таком очень слабо типизированном векторе. Вы приводите указатель void к указателю байта (в качестве примера), а затем используете арифметику указателя.
void* at(Vector* v, size_t index) {
// C needs manual sanity checks for the preconditions
if (NULL == v) return NULL;
if (v->size <= index) return NULL;
if (NULL == v->data) return NULL;
// now we can be (reasonably, as much as we can tell) sure, we did not get garbage as arguments...
return ((char*)(v->data)) + index * v->element_size;
}
К вашему сведению,
memmove(data2,vector->data,new_size * vector->element_size);
, вызывает неопределенное поведение.data2
является неопределенным и указывает на недоступную для записи память (о которой все равно знают). Я бы, пожалуй, начал с этого.