Un'alternativa è utilizzare una struttura dati che si faccia carico del lavoro al posto tuo. Per questo scopo di solito si utilizza un tipo di struttura dati chiamato vector o resizeable array. In C non sono così comodi perché normalmente costringono a utilizzare metodi per inserimento e lookup. Un trick che puoi usare per simulare un normale array è basato sui puntatori, se vuoi un esempio eccone uno qui sotto
codice:
#include <stdlib.h>#include <stdio.h>
#include <stdbool.h>
#include <string.h>


typedef struct Prodotto {
	char *id_prodotto;
	char *nome;
	float *prezzo;
} Prodotto;


void clean_input_buffer(void) {
	while (getchar() != '\n');
}


#define INITSIZE        4
#define EXPANDFACTOR    2
#define CONTRACTRATIO  0.25
#define TYPE Prodotto


typedef TYPE *vector;


static vector box(int *p) {
	return (vector)(p + 2);
}


static int *unbox(vector p) {
	return (((int *)p) - 2);
}


vector new_vector_init(int length){
	int size = INITSIZE;
	while (size < length) size *= EXPANDFACTOR;
	int *ret = (int *)malloc(sizeof(TYPE)*(size)+sizeof(int) * 2);
	ret[0] = size;
	ret[1] = length;
	return box(ret);
}


#define new_vector() new_vector_init(0)


void resize(vector *v, int length) {
	int *ret = unbox(*v);
	ret[1] = length;
	int size = ret[0];
	while (length != 0 && length / (double)size < CONTRACTRATIO) size /= EXPANDFACTOR;
	while (size < length) size *= EXPANDFACTOR;
	if (size != ret[0]) {
		ret = (int *)realloc(ret, sizeof(TYPE)*(size)+sizeof(int) * 2);
		*v = box(ret);
	}
}


int length(vector v) {
	return *(((int *)v) - 1);
}


int size(vector v) {
	return *(((int *)v) - 2);
}


void push(vector *v, TYPE e) {
	int len = length(*v);
	resize(v, len + 1);
	(*v)[len] = e;
}


void free_vector(vector v) {
	free(unbox(v));
}


TYPE pop(vector *v) {
	int len = length(*v) - 1;
	TYPE ret = (*v)[len];
	resize(v, len);
	return ret;
}


#define MAXN 20
vector inserimento();
vector inserimentoN();
void stampa(vector p);


int main(void) {
	stampa(inserimento());
	puts("\n");
	stampa(inserimentoN());
	printf("Premi invio per terminare");
	getchar();
	return 0;
}


vector inserimento() {
	vector ret = new_vector();
	int i = 0;
	char buffer[MAXN];
	float b;
	while (true) {
		printf("\nHai inserito %d prodotti, vuoi inserirne un altro? (s/n): ", i);
		char answer;
		do {
			answer = getchar();
		} while (answer != 'n' && answer != 's');
		clean_input_buffer();
		if (answer == 'n') break;
		Prodotto p;
		printf("\nId prodotto: ");
		scanf("%s", buffer);
		p.id_prodotto = (char *)malloc((strlen(buffer) + 1)*sizeof(char));
		strcpy(p.id_prodotto, buffer);
		printf("Nome: ");
		scanf("%s", buffer);
		p.nome = (char *)malloc((strlen(buffer) + 1) * sizeof(char));
		strcpy(p.nome, buffer);
		printf("Prezzo: ");
		scanf("%f", &b);
		p.prezzo = (float *)malloc(sizeof(float));
		*p.prezzo = b;
		push(&ret, p);
		i++;
	}
	return ret;
}


vector inserimentoN() {
	int nprodotti;
	puts("Quanti prodotti vuoi inserire?");
	scanf("%d", &nprodotti);
	vector ret = new_vector_init(nprodotti);
	int i = 0;
	char buffer[MAXN];
	float b;
	while (i < nprodotti) {
		printf("\nInserisci il prodotto %d:", i);
		printf("\nId prodotto: ");
		scanf("%s", buffer);
		ret[i].id_prodotto = (char *)malloc((strlen(buffer) + 1) * sizeof(char));
		strcpy(ret[i].id_prodotto, buffer);
		printf("Nome: ");
		scanf("%s", buffer);
		ret[i].nome = (char *)malloc((strlen(buffer) + 1) * sizeof(char));
		strcpy(ret[i].nome, buffer);
		printf("Prezzo: ");
		scanf("%f", &b);
		ret[i].prezzo = (float *)malloc(sizeof(float));
		*ret[i].prezzo = b;
		i++;
	}
	clean_input_buffer();
	return ret;
}


void stampa(vector p) {
	int len = length(p);
	int i = 0;
	while (i<len) {
		printf("Id_prodotto: %s\n", p[i].id_prodotto);
		printf("Nome: %s\n", p[i].nome);
		printf("Prezzo: %f \n", *p[i].prezzo);
		i++;
	}
}
Altrimenti il metodo più convenzionale è usare una struttura con 3 campi: lunghezza, dimensione e il puntatore a memoria e manipolare il contenuto attraverso dei metodi.

Se hai accesso alla libreria C++ la soluzione ovvia è usare il template vector