Reputation: 9
I'm currently trying to convert a bitmap image file into a 3D array. The program gets stuck after 3 tries after start = 1. I don't know what's wrong.
uint8_t*** loadImage(const int size[3], const char url[]){
uint8_t*** output_array;
output_array = malloc((size[0] * size[1]) * 3 * sizeof(uint8_t));
uint8_t byte, RGB = 0, start = 0; int x = 0, y = 0; uint64_t i;
FILE * fp = fopen(url, "rb");
if(fp == NULL){
perror("Unable to open file!");
exit(1);
} else {
for(i = 0; y < size[1]; i++) {
fread(&byte, sizeof(uint8_t), 1, fp);
if(start == 1){
//it ends here in the 3rd rotation after start
if(RGB < 2){
output_array[x][y][RGB] = byte;
RGB++;
}
else if (RGB >= 2 && x <= size[0]) {
x++;
output_array[x][y][0] = byte;
RGB = 1;
}
else if(RGB >= 2 && x > size[0]){
y++;
x = 0;
RGB = 1;
output_array[0][y][0] = byte;
}
}
else if(byte == 255 && start == 0){
//trying to find the starting position, which is always white(255)
start = 1;
}
}}
fclose(fp);
return output_array;
}
Upvotes: 0
Views: 483
Reputation: 223454
The statement output_array = malloc((size[0] * size[1]) * 3 * sizeof(uint8_t));
allocates space for size[0] * size[1] * 3
objects of type uint8_t
. But output_array
does not point to a uint8_t
. Its type is uint8_t ***
, which means it points to a uint8_t **
. Therefore, it would need space for objects of type uint8_t **
.
Somebody may have told you that arrays are pointers, and therefore uint8_t ***
is a three-dimensional array. That is false. uint8_t ***
is a pointer to a pointer to a pointer to a uint8_t
(or, if they point to the first of several objects, then a pointer to pointers to pointers to several uint8_t
). From your code, it looks like you want a multidimensional array, not a bunch of pointers.
The “easiest” way to do a malloc
correctly is to declare a pointer to the thing you want. If you want an array of size[0]
arrays of size[1]
arrays of 3 arrays of uint8_t
, then that would be declared with uint8_t SomeName [size[0]] [size[1]] [3];
1, and a pointer to it would be declared with uint8_t (*Pointer) [size[0]] [size[1]] [3];
. Then you can allocate space with2:
uint8_t (*Pointer) [size[0]] [size[1]] [3] = malloc(sizeof *Pointer);
Using the thing a pointer points to requires using the *
operator, so you would access array elements with (*Pointer)[i][j][k]
. However, you can instead make yourself a pointer to the first element of the array with:
uint8_t (*Pointer) [size[0]] [size[1]] [3] = malloc(sizeof *Pointer);
uint8_t (*output_array) [size[1]] [3] = *Pointer;
This first sets Pointer
to point to one three-dimensional array. Then *Pointer
designates the first array, but it is automatically converted to be a pointer to the first element of that array. (That first element is a two-dimensional array.) This pointer is used to initialize output_array
, so output_array
points to the first of several two-dimensional arrays. Now you can use output_array
much as if it were an array: You can refer to an element as output_array[i][j][k]
and return that pointer with return output_array
.
However, most people do not use the two lines as I have shown them above. I used that for illustration, to show the single object we want to allocate (a three-dimensional array) and how sizeof
calculates its size for you. Most people consolidate these lines into one line that allocates several of the contained arrays, with the same effect:
uint8_t (*output_array) [size[1]] [3] = malloc(size[0] * sizeof *output_array);
An array defined with uint8_t [size[0]] [size[1]] [3]
is a variable length array, because its dimensions are not integer constants as defined by the C standard. Such a type is called a variably modified type, and so is a pointer to it. Functions cannot return variably modified types, so this function cannot return a pointer to the array it created, or even a pointer to its first element, since the subarray is also variably modified.
A workaround is to convert the pointer to void *
and return that. First, modify the array declaration to return void *
:
void *loadImage(const int size[3], const char url[])
Then, in the caller, put the return value in a pointer of the desired type:
uint8_t (*output_array) [size[1]] [3] = loadImage(size, url);
Another option is to have the caller pass the address of a pointer which they would like to be set to point to the new array (by its first element). The function would change to:
void loadImage(const int size[3], const char url[],
uint8_t (**returned_array) [size[1]] [3])
{
…
*returned_array = output_array;
}
and the caller would change to:
uint8_t (*output_array) [size[1]] [3];
loadImage(size, url, &output_array);
1 The spaces between the brackets are just for readability, since there are so many brackets here. You can use that spacing in source code or not, as desired.
2 This requires that your C implementation support variable-length arrays. Those are currently optional in the C standard but are supported by common C compilers, although they might not be when compiling for C++.
Upvotes: 1