【问题标题】:Order of includes with standard libraries and local headers包含标准库和本地头文件的顺序
【发布时间】:2021-02-11 16:02:53
【问题描述】:

我正在生成位图文件。该程序每次都会编译,但在某些时候取决于#include 的顺序,它给了我好的或损坏的 .bmp 文件。
它来自一个教程,所有文件都是here

我的结构如下:

main.cpp

#include <iostream>
#include "Bitmap.h"
using namespace std;
int main() {
    Bitmap bitmap(800, 600);
    bitmap.write("test.bmp");
}

位图.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};
};

位图.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 &lt;fstream&gt; 排在最后时,它仍然编译没有错误,但会生成损坏的文件。
BitmapInfoHeader.hBitmapInfoHeader.h 只是持有一个结构,每个结构中都有一些 int32_t 变量,不要根本不使用fstream
为什么会这样?

【问题讨论】:

  • 可能是Bitmap*.h 头文件之一中的错误。
  • #pragma pack(2) 如果在标准头文件之前包含将导致损坏,因为这将改变已构建(标准)库的布局。更改包装后应尽快将包装重置为默认值

标签: c++ file-io std fstream


【解决方案1】:

错误在 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 也有一些相同的问题。

【讨论】:

  • 感谢您的详细解答。这有点超出我的知识范围,但为什么不包括 #pragma pack(pop) 违反了单一定义规则?我试图阅读一些docs,但这些示例有时包含它,有时不包含它,我不太明白
  • 其他头文件是库头文件。一些库已经编译,其定义使用默认打包,而您的应用程序具有不同打包的定义。这导致受影响的结构实际上有 2 种不同的定义,一种在预编译的静态或动态库中,另一种在使用结构的模块中。
【解决方案2】:

没有mcve 就无法确定。但很多时候,当包含顺序中断编译时,它是由至少一个头文件未能包含其所有依赖项引起的。在这种情况下,如果另一个包含缺失依赖项的标题恰好包含在损坏的标题之前,则会导致该错误被隐藏。

编辑添加的代码:

如果将标准标题移动到#pragma pack(2) 下方,则会破坏标准类定义。您应该始终在标题中推送和弹出包装编译指示。

【讨论】:

  • 我链接了这些文件,因为我不确定如何缩短它们(这是一个小仓库)。似乎问题出在@Elijay 所述的Bitmap*.h 文件中,但我不知道为什么。
  • @Jarartur I'm not sure how to shorten them 删除一些东西。如果问题仍然存在,请删除更多内容。如果出现另一个问题或问题解决,则恢复删除。重复直到没有东西可以删除。
  • 我尝试尽可能多地删除它以使其正常工作,但它仍然很长。以供将来参考,是否可以在堆栈上发布类似的碎片代码?
  • @Jaartur 它应该是完整的,即不需要添加,它应该重现问题并且应该尽可能短。所有这些都大大增加了您获得有用答案的机会。
  • @Jarartur Richard Critten 提到的编译指示包问题可能会导致问题。
猜你喜欢
  • 1970-01-01
  • 2015-10-18
  • 1970-01-01
  • 2021-04-28
  • 2011-07-30
  • 2014-12-24
  • 1970-01-01
  • 1970-01-01
  • 2019-06-12
相关资源
最近更新 更多