Reputation: 51
I'm doing it based on:
https://en.wikipedia.org/wiki/BMP_file_format
I want to create a BMP image from scratch in C.
#include <stdio.h>
#include <stdlib.h>
typedef struct HEADER {
short FileType;
int FileSize;
short R1;
short R2;
int dOffset;
} tp_header;
int main () {
FILE *image;
image = fopen("test.bmp", "w");
tp_header bHeader;
bHeader.FileType = 0x4D42;
bHeader.FileSize = 70;
bHeader.R1 = 0;
bHeader.R2 = 0;
bHeader.dOffset = 54;
fwrite(&bHeader, sizeof(struct HEADER), 1, image);
return 0;
}
I should be getting at output file:
42 4D 46 00 00 00 00 00 00 00 36 00 00 00
But instead i get:
42 4D 40 00 46 00 00 00 00 00 00 00 36 00 00 00
First of it should contain only 14 bytes. That "40 00" ruins it all. Is that the propper way of setting the header in C? How else can i limit the size in bytes outputed?
Upvotes: 0
Views: 195
Reputation: 12263
A struct
might include padding bytes between the fields to align the next field to certain address offsets. The values of these padding bytes are indetermined. A typical layout might look like:
struct {
uint8_t field1;
uint8_t <padding>
uint8_t <padding>
uint8_t <padding>
uint32_t field2;
uint16_t field3;
uint8_t <padding>
uint8_t <padding>
};
<padding>
is just added by the compile; it is not accessible by your program. This is just an example. Actual padding may differ and is defined by the ABI for your architecture (CPU/OS/toolchain).
Also, the order in which the bytes of a larger type are stored in memory (endianess) depends on the architecture. However, as the file requires a specific endianess, this might also have to be fixed.
Some - but not all - compilers alow to specify a struct
to be packed
(avoid padding), that still does not help with the endianess-problem.
Best is to serialize the struct properly by shifts and store to an uint8_t
-array:
#include <stdint.h>
/** Write an uint16_t to a buffer.
*
* \returns The next position in the buffer for chaining.
*/
inline uint8_t *writeUInt16(uint8_t *bp, value)
{
*bp++ = (uint8_t)value;
*bp++ = (uint8_t)(value >> 8);
return bp;
}
// similar to writeUInt16(), but for uint32_t.
... writeUInt32( ... )
...
int main(void)
{
...
uint8_t buffer[BUFFER_SIZE], *bptr;
bptr = buffer;
bptr = writeUInt16(bptr, 0x4D42U); // FileType
bptr = writeUInt32(bptr, 70U); // FileSize
...
}
That will fill buffer
with the header fields. BUFFER_SIZE
has to be set according to the header you want to create. Once all fields are stored, write buffer
to the file.
Declaring the functions inline
hints a good compiler to create almost optimal code for constants.
Note also, that the sizes of short
, etc. are not fixed. Use stdint.h
types is you need types of defined size.
Upvotes: 1
Reputation: 5619
The problem is that your struct is aligned. You ought to write it like
#pragma pack(push, 1)
typedef struct HEADER {
short FileType;
int FileSize;
short R1;
short R2;
int dOffset;
} tp_header;
#pragma pack(pop)
Just for you to know — the compiler for optimizing reasons by default would lay it out like:
typedef struct HEADER {
short FileType;
char empty1; //inserted by compiler
char empty2; //inserted by compiler
int FileSize;
short R1;
short R2;
int dOffset;
} tp_header;
But you actually made also another error: sizeof(int) ≥ 4 bytes
. I.e. depending on a platform integer could be 8 bytes. It is important, in such a cases you have to use types like int32_t
from cstdint
Upvotes: 0