Array dinâmico

Um array carateriza-se por ser uma sequência de objetos alojados em posições de memória contíguas. Tem como principais vantagens, a possibilidade de acesso direto aos elementos a custo unitário e, quando ordenado, permite pesquisa dicotómica a custo \(log_2 N\).

Designa-se por array estático um array cuja dimensão não pode ser alterada durante a execução do programa. Um array estático pode ser definido em tempo de compilação – os arrays próprios da linguagem – ou ser definidos em tempo de execução – os arrays de âmbito local alojados em stack ou os alojados dinamicamente com a função malloc.

Designa-se por array dinâmico um array cuja dimensão pode variar durante a execução do programa. A sua concretização basea-se na realocação da memória de suporte de dados à medida da necessidade. Por exemplo, a escrita numa posição além da dimensão atual do array, pode desencadear uma operação de subtituição da zona da memória de suporte de dados por uma zona de memória de dimensão maior.

Nesta versão do programa contador de palavras é utilizado um array dinâmico de objetos do tipo

struct word {
	char *text;
	int counter;
};

O array é referenciado pelo ponteiro words e representado por uma estrutura struct vector criada dinamicamente.

struct vector *words;

O ciclo de processamento de palavras consiste na leitura de palavra (linha 7), na procura da palavra (linha 10) e pela inserção, se a palavra ainda não estiver presente (linhas 15 a 17).

Listagem 105 Contagem de palavras baseada em array dinâmico
 1int main(int argc, char *argv[]){
 2	FILE *fd = fopen(argv[1], "r");
 3	int nwords = 0;
 4	words = vector_create(sizeof (struct word), 2);
 5	long initial = get_time();
 6	char *word_text;
 7	while ((word_text = read_word(fd, separators)) != NULL) {
 8		nwords++;
 9		size_t index;
10		if (vector_sorted_search(words, word_text, word_cmp_text, &index)) {
11			struct word *w = vector_at(words, index);
12			w->counter++;
13		}
14		else {
15			struct word w = { .counter = 1 };
16			w.text = strdup(word_text);
17			vector_insert(words, &w, index);
18		}
19	}
20	long duration = get_time() - initial;
21	fclose(fd);
22	printf("Total de palavras = %d; Palavras diferentes = %zd Time = %ld\n",
23			nwords, vector_size(words), duration);
24	vector_sort(words, word_cmp_count_decrease);
25	print_top_ten(words, min(10, vector_size(words)));
26	vector_foreach(words, free_word, NULL);
27	vector_destroy(words);
28}

Criação do array

O array dinâmico é criado pela função vector_create (linha 4). O primeiro argumento é a dimensão dos elementos do array e o segundo argumento é o acréscimo, em número de elementos, quando o array é aumentado. A utilização do valor 2 é por acaso.

O array dinâmico genérico é baseado na struct vector.

struct vector {
	void *buffer;
	size_t element_size, current_size, max_size, chunk_size;
};
  • O campo buffer aponta a zona da memória de dados do array.

  • O campo element_size a dimensão dos elementos do array, em número de bytes.

  • O campo current_size o atual número de elementos.

  • O campo max_size a capacidade da memória alocada, em número de elementos.

  • O campo chunck_size o acréscimo, em número de elementos, quando o array for aumentado.

vector_create

A função vector_create aloca memória para a struct vector (linha 2) e para a zona de dados do array (linha 5).

 1struct vector *vector_create(size_t element_size, size_t chunk_size)
 2{
 3	struct vector *vector = malloc(sizeof *vector);
 4	if (NULL == vector)
 5		return NULL;
 6	vector->buffer = malloc(element_size * chunk_size);
 7	if (NULL == vector->buffer) {
 8		free(vector);
 9		return NULL;
10	}
11	vector->element_size = element_size;
12	vector->chunk_size = chunk_size;
13	vector->max_size = chunk_size;
14	vector->current_size = 0;
15	return vector;
16}

Procura da palavra

Cada palavra lida do texto é procurada no array. O array é mantido ordenado para permitir a utilização de pesquisa dicotómica que tem custo \(log_2 N\).

A procura é realizada pela função vector_sorted_search. Recebe como argumentos a palavra a procurar e a função de comparação. Devolve duas informações: se encontrou – o índice onde se encontra; ou se não encontrou – o índice onde deveria encontrar.

Inserção de palavra

A inserção de uma nova palavra dá-se nas linhas 15 a 17. Ocorre depois de se verificar que a palavra ainda não existe no array.

Na inserção é utilizada a variável local w alojada em stack (linha 15) onde se concentra a informação relativa à palavra – o ponteiro para o texto da palavra e o contador. A inserção é realizada pela função vector_insert, que recebe o ponteiro para w e copia o conteúdo apontado para a posição index do array.

É utilizado o valor de index devolvido pela função vector_sorted_search com a posição do array onde o elemento deve ser inserido para manter a ordenação. Este aproveitamento dispensa uma nova procura da posição de inserção.

vector_insert

A função vector_insert insere o elemento apontado pelo parâmetro element na posição indicada por index.

Se essa posição estiver aquém da dimensão atual (linha 14), os elementos das posições seguintes são deslocados uma posição para a frente (linhas 21 a 23).

Devido a este deslocamento, se toda a memória alocada estiver utilizada, é necessário aumentar a porção de memória alocada (linha 15).

Se essa posição estiver além da memória alocada (linha 4), a alocação de memória é aumentada até cobrir a posição indicada (linhas 5 e 6).

O aumento de memória afeta aos dados do array implica alocar um novo bloco, copiar os dados do bloco antigo para o novo bloco e libertar o bloco antigo.

A função realloc, pertencente à biblioteca normalizada da linguagem C, satisfaz esta necessidade. Recebe o ponteiro para um bloco de memória previamente alocado e uma nova dimensão e altera a dimensão desse bloco para essa dimensão, mantendo o conteúdo de dados.

 1bool vector_insert(struct vector *vector, void *element, size_t index)
 2{
 3	char *buffer = vector->buffer;
 4	if (index >= vector->current_size) {
 5		if (index >= vector->max_size) {
 6			vector->max_size = (index / vector->chunk_size + 1) * vector->chunk_size;
 7			vector->buffer = buffer = realloc(buffer, vector->max_size * vector->element_size);
 8			if (NULL == buffer)
 9				return false;
10		}
11		memcpy(buffer + index * vector->element_size, element, vector->element_size);
12		vector->current_size = index + 1;
13		return true;
14	}
15	/* index < vector->current_size */
16	if (vector->current_size == vector->max_size) {
17		vector->max_size += vector->chunk_size;
18		vector->buffer = buffer = realloc(buffer, vector->max_size * vector->element_size);
19		if (NULL == buffer)
20			return false;
21	}
22	memmove(buffer + (index + 1) * vector->element_size,
23			buffer +  index * vector->element_size,
24			(vector->current_size - index) * vector->element_size);
25	memcpy(buffer + index * vector->element_size, element, vector->element_size);
26	vector->current_size++;
27	return true;
28}

Eliminação do array

Quando os dados no array e o próprio array não são mais necessários, deve-se libertar os recursos de memória associados. Essa libertação decorre em duas fases, a libertação dos elementos seguida da eliminação do próprio array. A primeira deve ser realizada no contexto de utilização, na Listagem 105, linha 26, são percorridas todas as posições do array, invocando em callback a função free_word. A segunda é realizada pela função vector_destroy (Listagem 105, linha 27).

vector_destroy

A função vector_destroy liberta a memória alocada para os dados do array (linha 3). Esta memória foi alocada na função vector_create utilizando a função malloc ou numa expansão utilizando a função realloc. Por último liberta a memória da própria struct vector.

1void vector_destroy(struct vector *vector)
2{
3	free(vector->buffer);
4	free(vector);
5}

vector_at

Devolve o ponteiro para a posição do array indicada pelo parâmetro index. A partir deste ponteiro, e aplicando aritmética de ponteiros, pode-se obter o ponteiro para outras posições do array.

1void *vector_at(struct vector *vector, int index)
2{
3	return (char *)vector->buffer + index * vector->element_size;
4}

vector_sort

Ordena o array por ordem crescente. Utiliza a função qsort pertencente à biblioteca normalizada da linguagem C, que aplica o algoritmo quick-sort.

A comparação dos elementos é realizada por uma função dada pelo programa utilizador – compare. A função retorna um valor positivo se o primeiro argumento for de ordem superior ao segundo, um valor negativo se for de ordem inferior e zero se forem da mesma ordem (iguais). A função de comparação, quando invocada, recebe os ponteiros para dois elementos do array que o algoritmo de ordenção determine comparar.

1void vector_sort(struct vector *vector, int (*compare)(const void *, const void *))
2{
3	qsort(vector->buffer, vector->current_size, vector->element_size, compare);
4}

vector_foreach

Itera sobre todas as posições do array, invocando a função passada no parâmetro do_it em cada iteração. Em cada invocação são passados como primeiro argumento o ponteiro para o elemento corrente do array e como segundo argumento o ponteiro recebido no parâmetro context. O parâmetro context é opaco do ponto de vista desta função e serve para o código utilizador passar informação de contexto à função do_it.

Os parâmetros da função do_it não são const para permitir a que esta função realize modificações aos elementos do array assim como ao objeto de contexto.

1void vector_foreach(struct vector *vector, void(*do_it)(void *, void *), void *context)
2{
3	for (size_t i = 0; i < vector->current_size; ++i)
4		do_it((char *)vector->buffer + i * vector->element_size, context);
5}