将位图字节写入二进制文件时出现奇怪的运行时不一致行为

Weird Runtime Inconsistent Behaviour While Writing Bitmap Bytes To Binary File

我写了一个原始的Bitmap Writer,它准备一个位图头,计算一个数据部分并将其写入二进制文件。

问题是,该程序适用于 800x600 分辨率,但不适用于某些任意分辨率。

位图编写器代码

#include <stdio.h>
#include <stdlib.h> // malloc()
#include <string.h>

/*
 * File Write , check this link : https://www.codingunit.com/c-tutorial-binary-file-io
 *
 */


typedef enum { FALSE, TRUE } boolean;

/* For 54 byte header data
 *
 * http://blog.paphus.com/blog/2012/08/14/write-your-own-bitmaps/
 */
 #pragma pack(push,1)
typedef struct BitmapHeaderType {
    unsigned char      b0;                      // 0
    unsigned char      b1;                      // 1
    unsigned int       fullFileSize;            // 2...5
    unsigned int       reserved;                // 6...9
    unsigned int       pixelOffset;             // 10..13 HEADER SIZE
    unsigned int       bitmapInfoHeaderSize;    // 14..17 BITMAP INFO HEADER SIZE
    unsigned int       pixelWidth;              // 18..21 image width
    unsigned int       pixelHeight;             // 22..25 image height
    unsigned short int numberOfColorPlanes;     // 26..27 the number of color planes
    unsigned short int bitPerPixel;             // 28..29 24bit, 32bit, bit size
    unsigned int       compessionState;         // 30..33 compression state, 0 for disable compression
    unsigned int       sizeOfRawPixel;          // 34..37 size of pixel data including paddings (24 bit padding changes data section)
    unsigned int       horizontalResolution;    // 38..41 just leave 2835
    unsigned int       verticalResolution;      // 42..45 just leave 2835
    unsigned int       numberOfColors;          // 46..49 set 0 for default
    unsigned int       numberOfImportantColors; // 50..53 set 0 for default
} BitmapHeader;
#pragma pack(pop)

void handleBitmapHeader(BitmapHeader *header, int width, int height);
int closestMultipleOfFour(int num);
void writeToFile(unsigned char* bytes, int len, char fileName[]);
void setPixel(unsigned char *data, int x, int y, unsigned char red, unsigned char green, unsigned char blue, int width, int height);

int main()
{
    //int width = 800; // compiling OK, runtime OK
    int width = 100; // compiling OK, runtime FAILED
    int height = 600;

    BitmapHeader *header  = (BitmapHeader *)malloc(sizeof(BitmapHeader));   

    handleBitmapHeader(header, width, height);

    unsigned char *data   = (unsigned char *)malloc(header->sizeOfRawPixel);
    unsigned char *bitmap = (unsigned char *)malloc(header->fullFileSize);

    // left top corner
    setPixel(data, 0        , 0         , 255,   0,   0, width, height);
    setPixel(data, 0        , 1         , 255,   0,   0, width, height);
    setPixel(data, 1        , 0         , 255,   0,   0, width, height);
    setPixel(data, 1        , 1         , 255,   0,   0, width, height);

    // right top corner
    setPixel(data, width-1  , 0         , 255,   0,   0, width, height);
    setPixel(data, width-1  , 1         , 255,   0,   0, width, height);
    setPixel(data, width-2  , 0         , 255,   0,   0, width, height);
    setPixel(data, width-2  , 1         , 255,   0,   0, width, height);

    // left bottom corner
    setPixel(data, 0        , height-1  , 255,   0,   0, width, height);
    setPixel(data, 0        , height-2  , 255,   0,   0, width, height);
    setPixel(data, 1        , height-1  , 255,   0,   0, width, height);
    setPixel(data, 1        , height-2  , 255,   0,   0, width, height);

    // right bottom corner
    setPixel(data, width-1  , height-1  , 255,   0,   0, width, height);
    setPixel(data, width-1  , height-2  , 255,   0,   0, width, height);
    setPixel(data, width-2  , height-1  , 255,   0,   0, width, height);
    setPixel(data, width-2  , height-2  , 255,   0,   0, width, height);

    // copy header to bitmap
    memcpy(bitmap, header, header->pixelOffset);

    // copy data to bitmap
    memcpy(bitmap+header->pixelOffset, data, header->fullFileSize);

    char fileName[] = "sampleBitmap.bmp";

    // write    
    writeToFile((unsigned char *)bitmap , header->fullFileSize, fileName);

    free(header);
    free(data);
    free(bitmap);

    return 0;
}

void handleBitmapHeader(BitmapHeader *header, int width, int height)
{
    // Calculate row with padding
    int rowWithPadding = closestMultipleOfFour(width*3);
    int rawSize = height * rowWithPadding;
    printf("Row With Padding : %4d\n", rowWithPadding);
    printf("Raw Size : Decimal[%4d], Hex[%4x]\n", rawSize, rawSize);

    header->b0 = 'B';
    header->b1 = 'M';
    header->fullFileSize = rawSize + 54;
    header->reserved = 0x00000000;
    header->pixelOffset = 54;
    header->bitmapInfoHeaderSize = 40;
    header->pixelWidth = (unsigned int)width;
    header->pixelHeight = (unsigned int)height;
    header->numberOfColorPlanes = 1;
    header->bitPerPixel = 24;
    header->compessionState = 0;
    header->sizeOfRawPixel = (unsigned int) rawSize;
    header->horizontalResolution = 0x2835;
    header->verticalResolution =   0x2835;
    header->numberOfColors = 0;
    header->numberOfImportantColors = 0;
}

int closestMultipleOfFour(int num)
{
    return (num + 3) & ~0x03;
}

void writeToFile(unsigned char* bytes, int len, char fileName[])
{

    FILE *filePtr;

    printf("HIT A : len = %d\n", len);

    filePtr = fopen(fileName, "wb");    // wb for write binary  

    printf("HIT B\n");

    if(!filePtr)
    {
        printf("ERROR::writeToFile()::Unable to open file : \"%s\"\n", fileName);
        return;
    }

    fwrite(bytes, len, 1, filePtr);

    fclose(filePtr);
}

void setPixel(unsigned char *data, int x, int y, unsigned char red, unsigned char green, unsigned char blue, int width, int height)
{
    y = height-1-y;

    int index = 0;
    int padSize = 0;

    if(x < width && y == 0)  // no need to calculate padding
        index = x * 3;
    else
    {
        boolean isPadding = ( (width*3) % 4 == 0) ? FALSE : TRUE;

        if(isPadding == TRUE) {
            padSize = closestMultipleOfFour(width * 3);

        } else {
            index = (y*width + x)*3;
        }
    }

    int bytes = 0;
    *(data + index+padSize + bytes++) = blue;
    *(data + index+padSize + bytes++) = green;
    *(data + index+padSize + bytes)   = red;

}

请通知以下行;

//int width = 800; // compiling OK, runtime OK
int width = 100; // compiling OK, runtime FAILED
int height = 600;

如果您 运行 喜欢 800x600 分辨率;

int width = 800; // compiling OK, runtime OK
/int width = 100; // compiling OK, runtime FAILED
int height = 600;

程序运行成功。并且还通知对于这两种情况,程序编译成功。

我尝试使用 gcc 和 MS VS,两种分辨率的行为是相同的。

我也试过用MS VS调试程序,但是在调试的时候,程序触发了一个断点,然后直接向我要一个dll(我不是MS VS Debugging with C的专家)

似乎有问题的部分是 writeToFile() 函数中存在的这一行;

filePtr = fopen(fileName, "wb");    // wb for write binary  

关于我哪里出错的任何想法?

程序崩溃,因为这行正在越界访问内存:

memcpy(bitmap + header->pixelOffset, data, header->fullFileSize);

我觉得你需要的是

memcpy(bitmap + header->pixelOffset, data, header->sizeOfRawPixel);

因为 bitmap 是一个包含 header 和原始数据的缓冲区。