我正在生成位图文件。该程序每次都编译,但取决于#include
在某些点的顺序,它给我好的或损坏的。bmp文件。
这是一个教程,所有的文件都在这里。
结构如下:
main.cpp
#include <iostream>
#include "Bitmap.h"
using namespace std;
int main() {
Bitmap bitmap(800, 600);
bitmap.write("test.bmp");
}
Bitmap.h
#include <string>
#include <cstdint>
#include <memory>
using namespace std;
class Bitmap {
private:
int m_width{0};
int m_height{0};
unique_ptr<uint8_t[]> m_pPixels{nullptr};
public:
Bitmap(int width, int height);
bool write(string filename);
};
BitmapFileHeader.h
#include <cstdint>
using namespace std;
#pragma pack(2)
struct BitmapFileHeader {
char header[2] { 'B', 'M' };
int32_t fileSize;
int32_t reserved { 0 };
int32_t dataOffset;
};
BitmapInfoHeader.h
#include <cstdint>
using namespace std;
#pragma pack(2)
struct BitmapInfoHeader {
int32_t headerSize{40};
int32_t width;
int32_t height;
int16_t planes{1};
int16_t bitsPerPixel{24};
int32_t compression{0};
int32_t dataSize{0};
int32_t horizontalResolution{2400};
int32_t verticalResolution{2400};
int32_t colors{0};
int32_t importantColors{0};
};
Bitmap.cpp
#include <fstream>
#include "Bitmap.h"
#include "BitmapInfoHeader.h"
#include "BitmapFileHeader.h"
using namespace std;
Bitmap::Bitmap(int width, int height): m_width(width), m_height(height), m_pPixels(new uint8_t[width * height * 3]{ }) {}
bool Bitmap::write(string filename) {
BitmapFileHeader fileHeader;
BitmapInfoHeader infoHeader;
fileHeader.fileSize = sizeof(BitmapFileHeader) + sizeof(BitmapInfoHeader) + m_width * m_height * 3;
fileHeader.dataOffset = sizeof(BitmapFileHeader) + sizeof(BitmapInfoHeader);
infoHeader.width = m_width;
infoHeader.height = m_height;
ofstream file;
file.open(filename, ios::out | ios::binary);
file.write((char *)&fileHeader, sizeof(fileHeader));
file.write((char *)&infoHeader, sizeof(infoHeader));
file.write((char *)m_pPixels.get(), m_width*m_height*3);
file.close();
return true;
}
现在,当在Bitmap.cpp
中,它是在这个顺序,一切都很好。当我改变顺序,使#include <fstream>
是最后一个,它仍然编译没有错误,但产生损坏的文件。BitmapInfoHeader.h
和BitmapInfoHeader.h
只是持有一个结构体,其中有一些int32_t
变量,根本不使用fstream
。
为什么呢?
错误在BitmapInfoHeader.h
// issue #1: There is no guard to prevent re-entry into you header.
#pragma once // this should fix that, you can also place a classic
// #ifndef / #define / #endif guard, as advised by the
// C++ Core Guidelines
// https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines
#include <cstdint>
using namespace std; // Issue #2:
// NEVER place a "using namespace std;" in a header
// file. BTW, it is not even needed for this
// file to compile.
// And NEVER place a "using namespace ...;"
// in the global namespace in a header file.
// issue # 3. Any pack pragma MUST be preceded by a push, like this:
#pragma pack(2) // <-- This pack pragma breaks all include
// files that will follow this one.
#pragma pack(push)
#pragma pack(1) // use pack(1) to signify full packing,
// so when someone else reads your code,
// they won't need to read the whole
// struct definition, to figure out
// what it is you are trying to
// accomplish.
struct BitmapInfoHeader {
int32_t headerSize{40};
int32_t width;
int32_t height;
int16_t planes{1};
int16_t bitsPerPixel{24};
int32_t compression{0};
int32_t dataSize{0};
int32_t horizontalResolution{2400};
int32_t verticalResolution{2400};
int32_t colors{0};
int32_t importantColors{0};
};
// After a #pragma(push), don't forget to restore the original
// packing, otherwise, you are violating the One Definition Rule.
// If you do not restore, everything after including this file
// will exhibit undefined behavior.
#pragma pack(pop)
文件Bitmap.h也有一些相同的问题。
没有mcve是不可能确定的。但是,当包含顺序中断编译时,通常是由于至少一个头文件未能包含其所有依赖项。在这种情况下,如果另一个包含缺失依赖的头文件恰好包含在被破坏的头文件之前,它会导致错误被隐藏。
编辑添加的代码:
如果将标准头移到#pragma pack(2)
下面,就破坏了标准类定义。你应该总是在头文件中push和pop packing pragma。