In my current project I'm writing a C function to write a BMP-File. Internally, the function is split up into three parts: write header, write info header, write data.
In some of these functions I do need to allocate heap space with malloc
and actually write to disk with fwrite
. These functions are doomed to fail from time-to-time (malloc
can't find a big enough memory chunk to allocate and fwrite
could fail as well).
Now I'm unsure of how to handle failure situations reasonably. My current approach is to have a returned integer value indicate success.
uint8_t BMP32_write(FILE *output, BMP32_FILE *bmp32_file) {
uint8_t res = 0;
res |= BMP32_write_header(output, bmp32_file->bmph);
res |= BMP32_write_info_header(output, bmp32_file->bmpih);
res |= BMP32_write_rgbData(output, bmp32_file->bmpih->biWidth, bmp32_file->bmpih->biHeight, bmp32_file->rgbData);
return res;
}
static uint8_t BMP32_write_header(FILE *output, BMP32_HEADER *h) {
uint8_t res = 0;
res |= !fwrite(&h->bfType , sizeof(h->bfType) , 1, output);
res |= !fwrite(&h->bfSize , sizeof(h->bfSize) , 1, output);
res |= !fwrite(&h->bfReserved, sizeof(h->bfReserved), 1, output);
res |= !fwrite(&h->bfOffBits , sizeof(h->bfOffBits) , 1, output);
return res;
}
static uint8_t BMP32_write_info_header(FILE *output, BMP32_INFO_HEADER *ih) {
uint8_t res = 0;
res |= !fwrite(&ih->biSize , sizeof(ih->biSize) , 1, output);
res |= !fwrite(&ih->biWidth , sizeof(ih->biWidth) , 1, output);
res |= !fwrite(&ih->biHeight , sizeof(ih->biHeight) , 1, output);
res |= !fwrite(&ih->biPlanes , sizeof(ih->biPlanes) , 1, output);
res |= !fwrite(&ih->biBitCount , sizeof(ih->biBitCount) , 1, output);
res |= !fwrite(&ih->biCompression , sizeof(ih->biCompression) , 1, output);
res |= !fwrite(&ih->biImageSize , sizeof(ih->biImageSize) , 1, output);
res |= !fwrite(&ih->biXPelsPerMeter, sizeof(ih->biXPelsPerMeter), 1, output);
res |= !fwrite(&ih->biYPelsPerMeter, sizeof(ih->biYPelsPerMeter), 1, output);
res |= !fwrite(&ih->biClrUsed , sizeof(ih->biClrUsed) , 1, output);
res |= !fwrite(&ih->biClrImportant , sizeof(ih->biClrImportant) , 1, output);
return res;
}
static uint8_t BMP32_write_rgbData(FILE *output, uint32_t width, uint32_t height, RGB_POINT *rgbData) {
uint32_t i, currRow, currColumn, bytesPerRow;
uint8_t *rawDataRow, res;
bytesPerRow = widthToBytesPerRow(width);
rawDataRow = calloc(bytesPerRow, sizeof(uint8_t));
if (rawDataRow != NULL) {
for (currRow = height; currRow > 0; currRow--) {
for (currColumn = 0, i = 0; currColumn < width; currColumn++) {
rawDataRow[i++] = rgbData[width*(currRow-1) + currColumn].blue;
rawDataRow[i++] = rgbData[width*(currRow-1) + currColumn].green;
rawDataRow[i++] = rgbData[width*(currRow-1) + currColumn].red;
}
res |= !fwrite(rawDataRow, bytesPerRow, 1, output);
}
free(rawDataRow);
} else {
res = 1;
}
return res;
}
What is a common way to handle situations like this? What would you consider doing?
2 Answers 2
To answer you question on how to handle this, one needs to address why your current approach is good or not. And the main issue I have with your code is that if either fwrite
fails, you'll happily continue until all three helper methods have completed. This is not good behaviour, and might trigger bigger errors which might terminate your program, removing your chance of correcting the initial errors.
According to this documentation fwrite
will return the number of bytes written, but you keep on or
'ing the result as if you're expecting 0
to indicate an OK write. Either way your or-ing will possibly hide the initial error code. I.e if the first fail returns 1
and the second fails with 2
you get an error code of 3
. Anyway the final error code is misleading as to why your code originally failed.
Two alternate ways to handle these are:
- Return at first fail
- Skip later executions of
fwrite
after fail
Return at first fail
The easiest way to handle this, is to make your own fwrite
, which in addition to the normal parameters has a result
flag variable initiated within BMP32_write
, where the local variant never executes the fwrite
if the flag variable is set. This removes the possibility for changing the error code, and re-executing the fwrite
after failing.
Skip later executions
An alternate version I've used in production code for a company is to have code which essentially behaves like the following:
res = res < 0 ? res : fwrite( ... );
res = res < 0 ? res : fwrite( ... );
res = res < 0 ? res : fwrite( ... );
/* Here I've used `res < 0` as the error situation, which might
need to be replaced with `res == 0` depending on your fwrite
implementation. */
If either fwrite
fails, it sets a value for res
and skips the other calls. This can be interleaved whenever you want to break out, i.e. it might be good to bail out BMP32_write
if either of the three helper functions fails. This can be prettified using macros, if you want to.
Expected return from fwrite
If your implementation of fwrite
returns number of bytes written, you need to compare it each time you write to the file to check if an error situation has arisen or not (compare written bytes with original block size). This can be easily achieved if you write your own fwrite
as suggested above.
If your implementation of fwrite
does indeed return 0
, you might have an issue if compiling your code on a different platform where they have a more standardized version of fwrite
...
-
\$\begingroup\$ Thanks for the answer. Since
fwrite
returns the number of elements written and I try to write one element at a time the possible return values are0
or1
. The or will keep the1
after each function call, but I do really like your variation with the conditional operator to skip further function calls (and prefer it over the extra function). What do you think of the return type? Isuint8_t
reasonably or should I use something like an enum like mentioned below? \$\endgroup\$LastSecondsToLive– LastSecondsToLive2016年02月12日 00:30:29 +00:00Commented Feb 12, 2016 at 0:30 -
\$\begingroup\$ Notice that
fwrite
returns the number of objects written not bytes. \$\endgroup\$LastSecondsToLive– LastSecondsToLive2016年02月12日 00:31:49 +00:00Commented Feb 12, 2016 at 0:31 -
\$\begingroup\$ Also how would the macro of the conditional operator look like? \$\endgroup\$LastSecondsToLive– LastSecondsToLive2016年02月12日 00:35:14 +00:00Commented Feb 12, 2016 at 0:35
-
\$\begingroup\$ @LastSecondsToLive, Even though it returns the object count with your parameters, it's a dangerous way of handling the error and you still allow for calling the fwrite after the first failure which is dangerous. Regarding return type it's usually better to keep that of
fwrite
as long as you don't "translate" it into an enum or similar. \$\endgroup\$holroy– holroy2016年02月12日 00:37:58 +00:00Commented Feb 12, 2016 at 0:37 -
\$\begingroup\$ The macro can have different appearances depending on how much you would like to hide... The entire
res = res > 0 ? res : fwrite ( ... )
could be within the macro (having the...
as macro parameter, or you could have only the start of the ternary,res > 0 ? res
within the macro. \$\endgroup\$holroy– holroy2016年02月12日 00:41:34 +00:00Commented Feb 12, 2016 at 0:41
Providing some kind of error information is IMO the right way to go. You have, however, used an uint8_t
as the return type, with possible values of 0
for success and 1
for failure.
I would recommend to use a different, more descriptive and more typesafe way. Possibilities are:
- A
typedef
for the int return type, plus symbolic constants (macros or constant values) to indicate success or error. - An enumeration type with properly named enumeration constants.
- Maybe more :-)
The typesafety, btw., is not part of C (C will happily convert all these values from and to), but is achieved since compilers and static code analysis tools will warn you about mixing these types.
Explore related questions
See similar questions with these tags.