This question already has answers here:
Large VLA overflow
(1 answer)
Getting a stack overflow exception when declaring a large array
(8 answers)
Closed 3 years ago.
I am trying to create a 2d array which each element points to a struct to store values from a CSV file. I am not sure if I am creating the struct or defining the 2d array right though.
Below is my struct and how I allocate the memory. There will always be 35 columns but the rows could grow quite large. When I run this with int test =29000 (or 29000 rows), it works and stores all values in a 2d array I can access by [row][col]. When I assign test with anything greater than 29000 it seg faults without even going into the allocation of the memeory it just segfaults at struct node* arrayofnodes[test][35];
I am very confused as to why it works with 29000 but not with 30000. Also, if anyone has any suggestions on how to malloc the array on one line instead of having to go into 2 for loops, I would be very happy to learn. Also I was wondering If I should use a typedef struct here or not.
Thanks
struct node {
char* value;
};
int test=30000;
struct node* arrayofnodes[test][35];
for (int p=0; p < test; p++){
for (int j=0; j < 35; j++){
arrayofnodes[p][j] = malloc(sizeof(arrayofnodes));
}
}//this works and i can access a certain element by arrayofnodes[row][col].value;
The problem is likely that you're allocating to much on the stack. arrayofnodes will be an array of 30,000*35=1,050,000 pointers, each of which is probably 8 bytes. So you're trying to allocate an array of 8MB on the stack, and 8MB is the default limit for stack size.
This code segfaulted for me:
#include <stdio.h>
int main() {
struct node {
char* value;
};
int test=30000;
struct node* arrayofnodes[test][35];
printf("%d\n", sizeof(arrayofnodes));
}
Also, sizeof(arrayofnodes) is wrong. If the code snippet above wouldn't have segfaulted, it would have printed 8400000. Instead, sizeof struct node or sizeof arrayofnodes[0][0] would give the right result.
A solution could be something like this:
struct node ** array[35];
for(int i=0; i<35; i++) {
array[i] = malloc(test * sizeof array[0]);
for(int j=0; j<test; j++) array[i][j] = malloc(sizeof (struct node));
}
You're allocating a huge amount of memory here: printf("%lu\n", sizeof(arrayofnodes)); reports that arrayofnodes has a size of 30000 * 35 * 8 = 8400000 bytes, making for a total memory allocation of 30000 * 35 * 8400000 = 8820000000000 bytes in your loop.
You might have meant *arrayofnodes[p][j]--allocate space for a pointer to a struct at row p and column j. Note that this doesn't include space for the memory block pointed to by the char *, which will also need to be malloced.
Also, remember that while the heap has plenty of space, the stack doesn't, and 8 * 35 * 30000 is likely more than a stack frame can handle.
Consider avoiding a struct if there's only one field. Using a plain old char * would avoid an extra layer of indirection.
If you'd like to maximize space efficiency at the expense of speed, you might try reading your text file in once to build an array of string sizes for each cell, then malloc everything accordingly for a second pass. You could use a char ***csv_data array containing cells allocated to size csv_cell_size[row][col] or similar.
Flattening your array by a dimension and using an offset to find a particular row/column is another option. Long story short, there are more than a few ways to manage this, with a lot depending on your data and how you plan to use it.
Also, don't forget to free() all your memory when you're done with it.
Your array declaration is being allocated on the stack, the default stack limit is 8MB. When test is 29000 the stack allocation is within the limit (29000 * 35 * 8 = 7.7MB), when you change test to 30000 you are exceeding the stack limit (30000 * 35 * 8 = 8.01MB) which results int the seg fault.
You can get past this by allocating the array on the heap using malloc, just remember to free what you allocate.
#include <stdio.h>
#include <malloc.h>
struct node {
char *value;
};
int rows = 30000;
int cols = 35;
struct node **arrayofnodes;
int main() {
arrayofnodes = (struct node **)malloc(rows * sizeof(struct node*));
for (int row = 0; row < rows; row++) {
arrayofnodes[row] = (struct node *)malloc(cols * sizeof(struct node*));
}
// Use arrayofnodes
// Free allocations after use...
}
Related
I have the following code snippet, which is giving me segmentation fault.
#include <stdio.h>
#include <stdlib.h>
struct node {
unsigned int n1;
unsigned int n2;
};
int main() {
struct node *nd = (struct node *)malloc(24356 * sizeof(struct node));
for(int i=0; i < 24356; i++) {
nd[i * sizeof(struct node)].n1 = i;
nd[i * sizeof(struct node)].n2 = i+1;
}
return 0;
}
When I did some debugging, I could find that the for loop was executing for some 3000 times, and then the segmentation fault occurs on this line:
nd[i * sizeof(struct node)].n1 = i;
I couldn't understand what is wrong in this code.
Can't malloc() allocate the amount of memory (~190 KB) I am asking it to allocate ?
If so, why doesn't it return NULL ?
These lines are the problem:
nd[i * sizeof(struct node)].n1 = i;
nd[i * sizeof(struct node)].n2 = i+1;
Change them to:
nd[i].n1 = i;
nd[i].n2 = i+1;
Remember, indices are not byte offesets. They're element indices. The compiler will automatically scale them by the size of an element.
I couldn't understand what is wrong in this code. Can't malloc() allocate the amount of memory (~190 KB) I am asking it to allocate ? If so, why doesn't it return NULL ?
There are several misunderstandings in your interpretation of the use of malloc that make your code incorrect:
malloc is a generic library routine that needs as parameter the amount of bytes to allocate and you correctly ask for space for 24356 cells by the size of one cell (64 bytes) or 1,558,784 bytes. (Around 1,5Mb, and not the 190kb you say in the question)
Then, you access the array elements by byte offset, this time erroneusly, as you have defined an array of structs, you cannot get the 24,356 (and later) array elements.
The expression you use inside the array index can range only from 0 to 24355, and no more.
Change
nd[i * sizeof(struct node)].n1 = i;
by
nd[i].n1 = i;
As arrays must be indexed by element position, and not by element address (respect to array origin)
This question already has answers here:
Dynamic array in C — Is my understanding of malloc and realloc correct?
(3 answers)
Closed 5 years ago.
So for my school project, a large CSV file will be entered through stdin and we will have to sort it based on column and print it out as a sorted csv file.
The step I am on right now is figuring out how to keep reallocing a struct of arrays so that it will grow if there is not big enough to hold the data coming in from stdin. We don't know the exact amount of rows that will be inputted in the CSV file. Right now we just used a static amount to test and see if the values are assigned to the structs.
I am still a beginner at C so I do not clearly know how I would iterate through a pointer like I would iterate through an array. Since we are using a static amount of structs in the array, we can just iterate using array[i] like in Java but how would you iterate through something like *array?
I do not know where to start for creating this dynamic array. I tried
struct array* testArray = (array*)malloc(sizeof(testArray));
but I have no idea how to iterate through it like I did with the static array by using array[i].
Any help would be greatly appreciated, sorry for the wall of text...
You can navigate through a malloced space the same way as with an array (using indicies), but it seems that your main issue lies in your use of malloc. Malloc's argument is the size in number of bytes that you want to allocate. So if you want to have an array of structs, you would first need to find out how many bytes one struct contains using sizeof(struct array), and then determine how large of an array you want, let's say N. So that line of code should look more like struct array* testArray = malloc(N * sizeof(struct array));. The return value of malloc will be a void pointer containing the memory address of the first byte of allocated space. Upon assigning this value to testArray, it will be type-casted to the assigned variable type (struct array *). Now you can use pointer arithmetic to access a specific index i with *(testArray + i), or simply testArray[i]. If you find that N was not a sufficient size, you can use realloc to increase the array size to 2N, or whatever size deemed necessary.
struct array* testArray = (array*)malloc(sizeof(testArray));
is a little wrong as you only allocate 1 element of testArray.
It is more like:
struct A
{
int a;
int b;
....
};
struct A* arr = malloc( N * sizeof(struct A) );
^^^
N element of struct A
int j;
for (j=0; j<N; ++j) // Iterate it like any other array
{
arr[j].a = 5;
arr[j].b = 42;
....
}
Use realloc when you need the array to grow.
When reading from a file/stdin it could look like (based on comment from David C. Rankin):
int n=0; // Count of the number of structs read from the file
struct A* arr = malloc( N * sizeof(struct A) );
while (read line from file)
{
arr[n].a = val1;
arr[n].b = val2;
++n; // Increment count
if (n == N) // Check if current array is full, i.e. realloc needed
{
// realloc array to 2 * N; N = N * 2
}
}
I have a struct defined like this
typedef struct {
char* Value;
unsigned int Length;
} MY_STRUCT;
I'm creating an array of these structs using calloc:
MY_STRUCT* arr = (MY_STRUCT*)calloc(50, sizeof(MY_STRUCT));
Then, in a loop, I'm accessing each struct and trying to allocate and assign a value to the Value field using calloc and memcpy:
int i;
for(i = 0; i < 50; i++)
{
MY_STRUCT myStruct = arr[i];
int valueLength = get_value_length(i);//for sake of example, we can assume that this function returns any value [1-99]
myStruct.Length = valueLength;
myStruct.Value = (char*) calloc(valueLength, sizeof(char));
memcpy(myStruct.Value, get_value(i), valueLength); //assume get_value(i) returns char* pointing to start of desired value
}
This code block crashes on the calloc line with Visual Studio indicating heap corruption. It doesn't fail the first time through the loop. Instead, it fails on the second pass when I'm trying to allocate a length 20 char array (first pass is length 5). I've tried using malloc as well, and I've tried using recommendations in:
Heap Corruption with malloc, struct and char *
Do I cast the result of malloc?
Nothing seems to mitigate the problem. I am originally a managed code programmer so my knowledge of memory allocation and management is not always the best. I'm sure I'm doing something boneheaded, but I'm not sure what. Any help would be greatly appreciated. Thank you!
I've been getting back into C for something, but I'm having trouble remembering much of how this memory management works. I'd like to have a pointer to an array of pointers to structures.
Say I have:
struct Test {
int data;
};
Then the array:
struct Test **array1;
Is this correct? My issue is working with this thing. So each pointer in the array points to something that is allocated separately. But I think I need to do this first:
array1 = malloc(MAX * sizeof(struct Test *));
I am having trouble understanding the above. Do I need to do this, and why do I need to do this? In particular, what does it mean to allocate memory for pointers if I am going to be allocating memory for each thing that the pointer points to?
Say now I have pointer to an array of pointers to structures. I now want it to point to the same array that I've created earlier.
struct Test **array2;
Do I need to allocate room for pointers like I did above, or can I just do:
array2 = array1
Allocated Array
With an allocated array it's straightforward enough to follow.
Declare your array of pointers. Each element in this array points to a struct Test:
struct Test *array[50];
Then allocate and assign the pointers to the structures however you want. Using a loop would be simple:
array[n] = malloc(sizeof(struct Test));
Then declare a pointer to this array:
// an explicit pointer to an array
struct Test *(*p)[] = &array; // of pointers to structs
This allows you to use (*p)[n]->data; to reference the nth member.
Don't worry if this stuff is confusing. It's probably the most difficult aspect of C.
Dynamic Linear Array
If you just want to allocate a block of structs (effectively an array of structs, not pointers to structs), and have a pointer to the block, you can do it more easily:
struct Test *p = malloc(100 * sizeof(struct Test)); // allocates 100 linear
// structs
You can then point to this pointer:
struct Test **pp = &p
You don't have an array of pointers to structs any more, but it simplifies the whole thing considerably.
Dynamic Array of Dynamically Allocated Structs
The most flexible, but not often needed. It's very similar to the first example, but requires an extra allocation. I've written a complete program to demonstrate this that should compile fine.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
struct Test {
int data;
};
int main(int argc, char **argv)
{
srand(time(NULL));
// allocate 100 pointers, effectively an array
struct Test **t_array = malloc(100 * sizeof(struct Test *));
// allocate 100 structs and have the array point to them
for (int i = 0; i < 100; i++) {
t_array[i] = malloc(sizeof(struct Test));
}
// lets fill each Test.data with a random number!
for (int i = 0; i < 100; i++) {
t_array[i]->data = rand() % 100;
}
// now define a pointer to the array
struct Test ***p = &t_array;
printf("p points to an array of pointers.\n"
"The third element of the array points to a structure,\n"
"and the data member of that structure is: %d\n", (*p)[2]->data);
return 0;
}
Output:
> p points to an array of pointers.
> The third element of the array points to a structure,
> and the data member of that structure is: 49
Or the whole set:
for (int i = 0; i < 100; i++) {
if (i % 10 == 0)
printf("\n");
printf("%3d ", (*p)[i]->data);
}
35 66 40 24 32 27 39 64 65 26
32 30 72 84 85 95 14 25 11 40
30 16 47 21 80 57 25 34 47 19
56 82 38 96 6 22 76 97 87 93
75 19 24 47 55 9 43 69 86 6
61 17 23 8 38 55 65 16 90 12
87 46 46 25 42 4 48 70 53 35
64 29 6 40 76 13 1 71 82 88
78 44 57 53 4 47 8 70 63 98
34 51 44 33 28 39 37 76 9 91
Dynamic Pointer Array of Single-Dynamic Allocated Structs
This last example is rather specific. It is a dynamic array of pointers as we've seen in previous examples, but unlike those, the elements are all allocated in a single allocation. This has its uses, most notable for sorting data in different configurations while leaving the original allocation undisturbed.
We start by allocating a single block of elements as we do in the most basic single-block allocation:
struct Test *arr = malloc(N*sizeof(*arr));
Now we allocate a separate block of pointers:
struct Test **ptrs = malloc(N*sizeof(*ptrs));
We then populate each slot in our pointer list with the address of one of our original array. Since pointer arithmetic allows us to move from element to element address, this is straight-forward:
for (int i=0;i<N;++i)
ptrs[i] = arr+i;
At this point the following both refer to the same element field
arr[1].data = 1;
ptrs[1]->data = 1;
And after review the above, I hope it is clear why.
When we're done with the pointer array and the original block array they are freed as:
free(ptrs);
free(arr);
Note: we do NOT free each item in the ptrs[] array individually. That is not how they were allocated. They were allocated as a single block (pointed to by arr), and that is how they should be freed.
So why would someone want to do this? Several reasons.
First, it radically reduces the number of memory allocation calls. Rather then N+1 (one for the pointer array, N for individual structures) you now have only two: one for the array block, and one for the pointer array. Memory allocations are one of the most expensive operations a program can request, and where possible, it is desirable to minimize them (note: file IO is another, fyi).
Another reason: Multiple representations of the same base array of data. Suppose you wanted to sort the data both ascending and descending, and have both sorted representations available at the same time. You could duplicate the data array, but that would require a lot of copying and eat significant memory usage. Instead, just allocate an extra pointer array and fill it with addresses from the base array, then sort that pointer array. This has especially significant benefits when the data being sorted is large (perhaps kilobytes, or even larger, per item) The original items remain in their original locations in the base array, but now you have a very efficient mechanism in which you can sort them without having to actually move them. You sort the array of pointers to items; the items don't get moved at all.
I realize this is an awful lot to take in, but pointer usage is critical to understanding the many powerful things you can do with the C language, so hit the books and keep refreshing your memory. It will come back.
It may be better to declare an actual array, as others have suggested, but your question seems to be more about memory management so I'll discuss that.
struct Test **array1;
This is a pointer to the address of a struct Test. (Not a pointer to the struct itself; it's a pointer to a memory location that holds the address of the struct.) The declaration allocates memory for the pointer, but not for the items it points to. Since an array can be accessed via pointers, you can work with *array1 as a pointer to an array whose elements are of type struct Test. But there is not yet an actual array for it to point to.
array1 = malloc(MAX * sizeof(struct Test *));
This allocates memory to hold MAX pointers to items of type struct Test. Again, it does not allocate memory for the structs themselves; only for a list of pointers. But now you can treat array as a pointer to an allocated array of pointers.
In order to use array1, you need to create the actual structs. You can do this by simply declaring each struct with
struct Test testStruct0; // Declare a struct.
struct Test testStruct1;
array1[0] = &testStruct0; // Point to the struct.
array1[1] = &testStruct1;
You can also allocate the structs on the heap:
for (int i=0; i<MAX; ++i) {
array1[i] = malloc(sizeof(struct Test));
}
Once you've allocated memory, you can create a new variable that points to the same list of structs:
struct Test **array2 = array1;
You don't need to allocate any additional memory, because array2 points to the same memory you've allocated to array1.
Sometimes you want to have a pointer to a list of pointers, but unless you're doing something fancy, you may be able to use
struct Test *array1 = malloc(MAX * sizeof(struct Test)); // Pointer to MAX structs
This declares the pointer array1, allocated enough memory for MAX structures, and points array1 to that memory. Now you can access the structs like this:
struct Test testStruct0 = array1[0]; // Copies the 0th struct.
struct Test testStruct0a= *array1; // Copies the 0th struct, as above.
struct Test *ptrStruct0 = array1; // Points to the 0th struct.
struct Test testStruct1 = array1[1]; // Copies the 1st struct.
struct Test testStruct1a= *(array1 + 1); // Copies the 1st struct, as above.
struct Test *ptrStruct1 = array1 + 1; // Points to the 1st struct.
struct Test *ptrStruct1 = &array1[1]; // Points to the 1st struct, as above.
So what's the difference? A few things. Clearly, the first method requires you to allocate memory for the pointers, and then allocate additional space for the structs themselves; the second lets you get away with one malloc() call. What does the extra work buy you?
Since the first method gives you an actual array of pointers to Test structs, each pointer can point to any Test struct, anywhere in memory; they needn't be contiguous. Moreover, you can allocate and free the memory for each actual Test struct as necessary, and you can reassign the pointers. So, for example, you can swap two structures by simply exchanging their pointers:
struct Test *tmp = array1[2]; // Save the pointer to one struct.
array1[2] = array1[5]; // Aim the pointer at a different struct.
array1[5] = tmp; // Aim the other pointer at the original struct.
On the other hand, the second method allocates a single contiguous block of memory for all of the Test structs and partitions it into MAX items. And each element in the array resides at a fixed position; the only way to swap two structures is to copy them.
Pointers are one of the most useful constructs in C, but they can also be among the most difficult to understand. If you plan to continue using C, it'll probably be a worthwhile investment to spend some time playing with pointers, arrays, and a debugger until you're comfortable with them.
Good luck!
I suggest that you build this out a layer at a time using typdefs to create layers of types. By doing so, the different types needed will be much clearer.
For instance:
typedef struct Test {
int data;
} TestType;
typedef TestType * PTestType;
This will create two new types, one for the struct and one for a pointer to the struct.
So next if you want an array of the structs then you would use:
TestType array[20]; // creates an array of 20 of the structs
If you want an array of pointers to the structs then you would use:
PTestType array2[20]; // creates an array of 20 of pointers to the struct
Then if you want to allocate structs into the array you would do something like:
PTestType array2[20]; // creates an array of 20 of pointers to the struct
// allocate memory for the structs and put their addresses into the array of pointers.
for (int i = 0; i < 20; i++) {
array2 [i] = malloc (sizeof(TestType));
}
C does not allow you to assign one array to another. You must instead use a loop to assign each element of one array to an element of the other.
EDIT: Another Interesting Approach
Another approach would be a more object oriented approach in which you encapsulate a few things. For instance using the same layers of types we create two types:
typedef struct _TestData {
struct {
int myData; // one or more data elements for each element of the pBlob array
} *pBlob;
int nStructs; // count of number of elements in the pBlob array
} TestData;
typedef TestData *PTestData;
Next we have a helper function which we use to create the object, named appropriately enough CreateTestData (int nArrayCount).
PTestData CreateTestData (int nCount)
{
PTestData ret;
// allocate the memory for the object. we allocate in a single piece of memory
// the management area as well as the array itself. We get the sizeof () the
// struct that is referenced through the pBlob member of TestData and multiply
// the size of the struct by the number of array elements we want to have.
ret = malloc (sizeof(TestData) + sizeof(*(ret->pBlob)) * nCount);
if (ret) { // make sure the malloc () worked.
// the actual array will begin after the end of the TestData struct
ret->pBlob = (void *)(ret + 1); // set the beginning of the array
ret->nStructs = nCount; // set the number of array elements
}
return ret;
}
Now we can use our new object as in the source code segment below. It should check that the pointer returned from CreateTestData() is valid however this is really just to show what could be done.
PTestData go = CreateTestData (20);
{
int i = 0;
for (i = 0; i < go->nStructs; i++) {
go->pBlob[i].myData = i;
}
}
In a truly dynamic environment you may also want to have a ReallocTestData(PTestData p) function that would reallocate a TestData object in order to modify the size of the array contained in the object.
With this approach, when you are done with a particular TestData object, you can just free the object as in free (go) and the object and its array are both freed at the same time.
Edit: Extending Further
With this encapsulated type we can now do a few other interesting things. For instance, we can have a copy function, PTestType CreateCopyTestData (PTestType pSrc) which would create a new instance and then copy the argument to a new object. In the following example, we reuse the function PTestType CreateTestData (int nCount) that will create an instance of our type, using the size of the object we are copying. After doing the create of the new object, we make a copy of the data from the source object. The final step is to fix up the pointer which in the source object points to its data area so that pointer in the new object now points to the data area of itself rather than the data area of the old object.
PTestType CreateCopyTestData (PTestType pSrc)
{
PTestType pReturn = 0;
if (pSrc) {
pReturn = CreateTestData (pSrc->nStructs);
if (pReturn) {
memcpy (pReturn, pSrc, sizeof(pTestType) + pSrc->nStructs * sizeof(*(pSrc->pBlob)));
pReturn->pBlob = (void *)(pReturn + 1); // set the beginning of the array
}
}
return pReturn;
}
Structs are not very different from other objects. Let's start with characters:
char *p;
p = malloc (CNT * sizeof *p);
*p is a character, so sizeof *p is sizeof (char) == 1; we allocated CNT characters. Next:
char **pp;
pp = malloc (CNT * sizeof *pp);
*p is a pointer to character, so sizeof *pp is sizeof (char*). We allocated CNT pointers. Next:
struct something *p;
p = malloc (CNT * sizeof *p);
*p is a struct something, so sizeof *p is sizeof (struct something). We allocated CNT struct somethings. Next:
struct something **pp;
pp = malloc (CNT * sizeof *pp);
*pp is a pointer to struct, so sizeof *pp is sizeof (struct something*). We allocated CNT pointers.
This question is a continuation of Malloc call crashing, but works elsewhere
I tried the following program and I found it working (i.e. not crashing - and this was mentioned in the above mentioned link too). I May be lucky to have it working but I'm looking for a reasonable explanation from the SO experts on why this is working?!
Here are some basic understanding on allocation of memory using malloc() w.r.t structures and pointers
malloc(sizeof(struct a) * n) allocates n number of type struct a elements. And, this memory location can be stored and accessed using a pointer-to-type-"struct a". Basically a struct a *.
malloc(sizeof(struct a *) * n) allocates n number of type struct a * elements. Each element can then point to elements of type struct a. Basically malloc(sizeof(struct a *) * n) allocates an array(n-elements)-of-pointers-to-type-"struct a". And, the allocated memory location can be stored and accessed using a pointer-to-(pointer-to-"struct a"). Basically a struct a **.
So when we create an array(n-elements)-of-pointers-to-type-"struct a", is it
valid to assign that to struct a * instead of struct a ** ?
valid to access/de-reference the allocated array(n-elements)-of-pointers-to-type-"struct a" using pointer-to-"struct a" ?
data * array = NULL;
if ((array = (data *)malloc(sizeof(data *) * n)) == NULL) {
printf("unable to allocate memory \n");
return -1;
}
The code snippet is as follows:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
typedef struct {
int value1;
int value2;
}data;
int n = 1000;
int i;
int val=0;
data * array = NULL;
if ((array = (data *)malloc(sizeof(data *) * n)) == NULL) {
printf("unable to allocate memory \n");
return -1;
}
printf("allocation successful\n");
for (i=0 ; i<n ; i++) {
array[i].value1 = val++;
array[i].value2 = val++;
}
for (i=0 ; i<n ; i++) {
printf("%3d %3d %3d\n", i, array[i].value1, array[i].value2);
}
free(array);
printf("freeing successful\n");
return 0;
}
EDIT:
OK say if I do the following by mistake
data * array = NULL;
if ((array = (data *)malloc(sizeof(data *) * n)) == NULL) {
Is there a way to capture (during compile-time using any GCC flags) these kind of unintended programming typo's which could work at times and might blow out anytime! I compiled this using -Wall and found no warnings!
There seems to be a fundamental misunderstanding.
malloc(sizeof(struct a) * n) allocates n number of type struct a elements.
No, that's just what one usually does use it as after such a call. malloc(size) allocates a memory region of size bytes. What you do with that region is entirely up to you. The only thing that matters is that you don't overstep the limits of the allocated memory. Assuming 4 byte float and int and 8 byte double, after a successful malloc(100*sizeof(float));, you can use the first 120 of the 400 bytes as an array of 15 doubles, the next 120 as an array of 30 floats, then place an array of 20 chars right behind that and fill up the remaining 140 bytes with 35 ints if you wish. That's perfectly harmless defined behaviour.
malloc returns a void*, which can be implicitly cast to a pointer of any type, so
some_type **array = malloc(100 * sizeof(data *)); // intentionally unrelated types
is perfectly fine, it might just not be the amount of memory you wanted. In this case it very likely is, because pointers tend to have the same size regardless of what they're pointing to.
More likely to give you the wrong amount of memory is
data *array = malloc(n * sizeof(data*));
as you had it. If you use the allocated piece of memory as an array of n elements of type data, there are three possibilities
sizeof(data) < sizeof(data*). Then your only problem is that you're wasting some space.
sizeof(data) == sizeof(data*). Everything's fine, no space wasted, as if you had no typo at all.
sizeof(data) > sizeof(data*). Then you'll access memory you shouldn't have accessed when touching later array elements, which is undefined behaviour. Depending on various things, that could consistently work as if your code was correct, immediately crash with a segfault or anything in between (technically it could behave in a manner that cannot meaningfully be placed between those two, but that would be unusual).
If you intentionally do that, knowing point 1. or 2. applies, it's bad practice, but not an error. If you do it unintentionally, it is an error regardless of which point applies, harmless but hard to find while 1. or 2. applies, harmful but normally easier to detect in case of 3.
In your examples. data was 4 resp. 8 bytes (probably), which on a 64-bit system puts them into 1. resp. 2. with high probability, on a 32-bit system into 2 resp. 3.
The recommended way to avoid such errors is to
type *pointer = malloc(num_elems * sizeof(*pointer));
No.
sizeof(struct a*) is the size of a pointer.
sizeof(struct a) is the size of the entire struct.
This array = (data *)malloc(sizeof(data *) * n) allocates a sizeof(data*) (pointer) to struct data, if you want to do that, you need a your array to be a data** array.
In your case you want your pointer to point to sizeof(data), a structure in memory, not to another pointer. That would require a data** (pointer to pointer).
is it valid to assign that to struct a * instead of struct a ** ?
Well, technically speaking, it is valid to assign like that, but it is wrong (UB) to dereference such pointer. You don't want to do this.
valid to access/de-reference the allocated array(n-elements)-of-pointers-to-type-"struct a" using pointer-to-"struct a" ?
No, undefined behavior.