As you sure know, a pointer is not exactly a type of data, meaning this capacity (or number of bytes used), but just a memory direction reference: a number (expressed as XXXXX:XXXXX the main cases and in the main SO as a 4-bytes number) that indicates memory page & offset inside that page. Memory referenced by that direction could had been allocated, or not. That's it, marked to SO that is reserved for use for your program. But in any case it's protected just for that variable: your program can use that memory block, and for example, assign it to any type of variable. If you allocate correct sized memory for your struct, and then reference to that memory address, you can use it with no difference as if you had been declared as a normal variable.
In this example, you can use without any difference line ST S[3] or ST *S = malloc(sizeof(ST)*3); As seen, sizeof(ST) will give to you 4(int) + 1(char B) + 20*1(char[20]) = 25 bytes, and with padding, 28. malloc(sizeof(ST)*3) will then reserve 84 bytes for your program (surely more, surely 128 bytes reserving whole page block) and will give to you memory direction of the first byte reserved. In fact, this malloc it's about the same as declaring ST S[3];
Because of big number of possibilites pointers allow, as direct access to memory are (for example, a big number of variables sharing same memory, or capacity to go up & down along memory block) they are then difficult to manage, and so, a big nest of bugs, preferring then more uplevel abstraction. But your question's answer is yes, it is possible.
#include <malloc.h>
typedef struct ST
{
int A;
char B;
char C[20];
} ST;
int main(int agrc, char *agrv[])
{
// ST S[3];
ST *S = malloc(sizeof(ST)*3);
// ST *S = malloc(100);
S[0].A = 1; S[0].B = '1'; S[0].C[0] = 'a';
S[0].C[1] = 'b'; S[0].C[2] = 'c'; S[0].C[3] = '\0';
S[1].A = 2; S[1].B = '2'; S[1].C[0] = 'A';
S[1].C[1] = 'B'; S[1].C[2] = 'C'; S[1].C[3] = '\0';
S[2].A = 3; S[2].B = '3'; S[2].C[0] = 'Z';
S[2].C[1] = 'z'; S[2].C[2] = 'Z'; S[2].C[3] = '\0';
int i=0;
for (i=0;i<3;i++)
{
printf("Struct %d\n",i);
printf("%d\n",S[i].A);
printf("%c\n",S[i].B);
printf("%s\n",&S[i].C);
printf("\n");
}
}
ADDED : This is the same, but from the other way : going to a direction of a string in memory, and interpreting it as it was our struct.
#include <malloc.h>
typedef struct ST
{
short A; // 2
char B; // 1
char C[20]; // 20
} ST;
int main(int agrc, char *agrv[])
{
char *s =malloc(28);
s[0] = 65; // Lower byte of int
s[1] = 65; // Higher byte of int
s[2] = 'C';
s[3] = 'a';
s[4] = 'b';
s[5] = 'c';
s[6] = 0x00;
printf("%s\n",s);
ST *T;
T = (void *)s;
printf("%d\n",T[0].A);
printf("%c\n",T[0].B);
printf("%s\n",T[0].C);
}