【问题标题】:C++ memcpy copy of object appears corrupted对象的 C++ memcpy 副本出现损坏
【发布时间】:2016-01-06 13:36:04
【问题描述】:

作为实现编译器的类项目的一部分,我还实现了一个哈希表,用作编译器的符号表。

哈希表的实现是非常底层的,一个手动打包的原始内存块,它只打算存储 Token 对象。因此,为了优化哈希表的可序列化,我决定简单地内联表中的 Token,即在第一次插入 Token 时简单地将 Token 对象 memcpy 到表的内存中。

我知道不应该 memcpy a class that has virtual functions or pointersgeneral using memcpy on objects of a class is bad practice。但是,从下面的声明中可以看出,Token 类没有虚函数或指针,如果这不是低级编程的练习,我不会使用 memcpy。

class Token {

    public:
        Token() : tag(BAD) {}
        Token(Tag t) : tag(t) {}
        Tag tag;
        size_t getSize(){ return sizeof(Token); }

};

我遇到的问题是哈希表正确插入了令牌,并且在查找相同的键时找到了相同的内存位置,但是,当我尝试访问返回的令牌指针的成员时,它似乎数据已损坏。

我编写了一个程序来测试一个简单输入的符号表。该程序执行以下操作:

  1. 将输入文件读入缓冲区。
  2. 通过将所有内置标记插入 Lexer 符号表来初始化 Lexer。
  3. 在输入时调用 Lexer 的 getToken 方法并打印 Token 的标签,直到读取 EOF Token。

虽然符号表返回它在内存中插入 Token 的相同位置,但 Token 的标记属性不再与插入的原始位置匹配。下面是程序在符号表中插入关键字program时的日志输出:

[debug]   Entering SymbolTable.insert(const char* key)
[debug]   bin: 48 Searching for the last element in this bin's linked list.
[debug]   Last entry found... writing to entry's next location field.
[debug]   Writing the new entry's identifier to the table.
[debug]   The identifier: program has been written to the table.
[debug]   The memory blocks are not equal prior to assignment.
[debug]   The memory blocks are equal.
[debug]   nextLoc: 571 tag: 46
[debug]   Location of Token: 14287688

下面是程序随后在符号表中查找标识符program时的日志输出。

[debug]   Entering Lexer.getToken()
[debug]   Entering SymbolTable.contains(const char* key)
[debug]   Entering SymbolTable.find(const char* key) key: program
[debug]   bin: 48
[debug]   Search location: 541
[debug]   Comparing key char: p to table char: p
[debug]   Comparing key char: r to table char: a
[debug]   Tag of entry: 1684368227
[debug]   Location of Token: 14287653
[debug]   Search location: 557
[debug]   Comparing key char: p to table char: p
[debug]   Comparing key char: r to table char: r
[debug]   Comparing key char: o to table char: o
[debug]   Comparing key char: g to table char: c
[debug]   Tag of entry: 1920296037
[debug]   Location of Token: 14287668
[debug]   Search location: 0
[debug]   Comparing key char: p to table char: p
[debug]   Comparing key char: r to table char: r
[debug]   Comparing key char: o to table char: o
[debug]   Comparing key char: g to table char: g
[debug]   Comparing key char: r to table char: r
[debug]   Comparing key char: a to table char: a
[debug]   Comparing key char: m to table char: m
[debug]   Tag of entry: 1207959598
[debug]   Location of Token: 14287688
The 1th token: 60

因此,从 Token 消息的位置可以看出,符号表在内存中找到它写入 Token 的相同位置,但 Token 的标记不同。我很困惑为什么会这样。

为了完整起见,这里是 SymbolTable 类的定义。

template<class sizeType>    
class SymbolTable{      

    public:    
        SymbolTable();                                                             
        ~SymbolTable();        
        Token* operator[](const char* key);    
        bool contains(const char* key);    
        Token* insert(const char* key, Token value);    

    private:    
        void* find(const char* key);                                                        
        static const size_t nbins = 64;    
        sizeType nextLoc;                                                      
        void* table;       
        size_t hash(const char* key){    
            return (size_t)key[0] % nbins;    
        }            

}; 

这里是符号表的插入、查找和操作符[]函数的源代码。

template<class sizeType> Token* SymbolTable<sizeType>::insert(const char* key, Token value){

    BOOST_LOG_TRIVIAL(debug) << "Entering SymbolTable.insert(const char* key)";
    size_t bin = hash(key);
    void *sizeType_ptr,
         *tbl_char_ptr,
         *idSize_ptr;
    unsigned char idSize = 0;
    const char *key_ptr = key;
    Token *token_ptr = NULL;

    // Find the last entry in this bin's linked list.
    BOOST_LOG_TRIVIAL(debug) << "bin: " << bin
                             << " Searching for the last element in this bin's linked list.";
    sizeType_ptr = table + sizeof(sizeType)*bin;

    while(*((sizeType*)sizeType_ptr) != 0){
        sizeType_ptr = table + *((sizeType*)sizeType_ptr);
    }

    BOOST_LOG_TRIVIAL(debug) << "Last entry found... writing to entry's next location field.";
    // Write the location of the new entry to this entry's next field.
    *((sizeType*)sizeType_ptr) = nextLoc;

    // Move to new entry's location.
    sizeType_ptr = table + nextLoc;

    // Write identifier
    BOOST_LOG_TRIVIAL(debug) << "Writing the new entry's identifier to the table.";
    tbl_char_ptr = sizeType_ptr + sizeof(sizeType) + sizeof(unsigned char);
    while(*key_ptr != '\0'){
        *((char*)tbl_char_ptr) = *key_ptr;
        tbl_char_ptr = tbl_char_ptr + sizeof(char);
        ++key_ptr;
        ++idSize;
    }

    BOOST_LOG_TRIVIAL(debug) << "The identifier: " << key << " has been written to the table.";

    // Write length of identifier.
    idSize_ptr = sizeType_ptr + sizeof(sizeType);
    *((unsigned char*)idSize_ptr) = idSize;
    token_ptr = (Token*)(tbl_char_ptr + sizeof(char));

    void *tk = &value,
         *tb = token_ptr;
    for(int i = 0; i < value.getSize(); ++i){
        if(*((char*)tk) != *((char*)tb)){
            BOOST_LOG_TRIVIAL(debug) << "The memory blocks are not equal prior to assignment.";
            break;
        }
    }

    memcpy((void*)token_ptr, &value, value.getSize());

    bool areEqual = true;
    for(int i = 0; i < value.getSize(); ++i){
        if(*((char*)tk) != *((char*)tb)){
            areEqual = false;
            BOOST_LOG_TRIVIAL(debug) << "The memory blocks are not after assignment.";
            break;
        }
    }
    if(areEqual){
        BOOST_LOG_TRIVIAL(debug) << "The memory blocks are equal.";
    }

    nextLoc = nextLoc + sizeof(sizeType) + sizeof(unsigned char)
                + idSize*sizeof(char) + value.getSize();
    BOOST_LOG_TRIVIAL(debug) << "nextLoc: " << nextLoc
                             << " tag: " << token_ptr->tag;
    BOOST_LOG_TRIVIAL(debug) << "Location of Token: " << (size_t)token_ptr;
    return token_ptr;

}

template<class sizeType>
void* SymbolTable<sizeType>::find(const char* key){

    BOOST_LOG_TRIVIAL(debug) << "Entering SymbolTable.find(const char* key) "
                             << "key: " << key;
    bool found = false;
    size_t bin = hash(key);
    void *idSize,
         *sizeType_ptr,
         *tbl_char_ptr,
         *result_ptr = NULL;
    const char* key_ptr = key;

    BOOST_LOG_TRIVIAL(debug) << "bin: " << bin;
    sizeType_ptr = table + sizeof(sizeType)*bin;


    while(!found){

        found = true;
        // Is this the last element in this bin?
        if(*((sizeType*)sizeType_ptr) == 0){ 
            result_ptr = NULL;
            return result_ptr;
        }
        // Advance to the next element in this bin's linked list.
        sizeType_ptr = table + *((sizeType*)sizeType_ptr);
        idSize = sizeType_ptr + sizeof(sizeType);
        tbl_char_ptr = idSize + sizeof(unsigned char);

        BOOST_LOG_TRIVIAL(debug) << "Search location: " << *((sizeType*)sizeType_ptr);
        // Check if the passed key matches the current key in the table.
        for(int i = 0; i < *((unsigned char*)idSize); ++i){

            BOOST_LOG_TRIVIAL(debug) << "Comparing key char: " << *key_ptr
                                     << "to table char: " << *((const char*)tbl_char_ptr);
            // Check if the key is too short or if the chars do not match.
            if(*key_ptr != *((const char*)tbl_char_ptr)){
                found = false;
                break;
            }   

            ++key_ptr;
            tbl_char_ptr = tbl_char_ptr + sizeof(char);
            BOOST_LOG_TRIVIAL(debug) << "*(const char*)tbl_char_ptr: "
                                     << *((const char*)tbl_char_ptr);

        }

        result_ptr = tbl_char_ptr + sizeof(char);
        BOOST_LOG_TRIVIAL(debug) << "Tag of entry: " << ((Token*)result_ptr)->tag;
        BOOST_LOG_TRIVIAL(debug) << "Location of Token: " << (size_t)result_ptr;
        key_ptr = key;

    }   

    return result_ptr;

}

template<class sizeType>
Token* SymbolTable<sizeType>::operator[](const char* key){

    BOOST_LOG_TRIVIAL(debug) << "Entering SymbolTable.operator[](const char* key)";
    void* void_ptr = find(key);
    Token* token_ptr = (Token*)void_ptr;
    return token_ptr;

}

这里是测试程序的源代码。

int main(){

    cout << "Executing testLexer.cpp" << endl;

    ifstream file("./pascalPrograms/helloworld.pas");
    string program((istreambuf_iterator<char>(file)), istreambuf_iterator<char>());
    cout << "program:\n\n" << program << endl;
    int fileSize = program.length();
    const char* buffer = program.c_str();

    const char* scanp = buffer;

    cout << "Instantiating Lexer" << endl << endl;
    Lexer lexer = Lexer(scanp);

    Token* tok;
    int i = 0;

    cout << "Entering while loop to fetch tags." << endl << endl;
    do{ 
        i++;
        tok = lexer.getToken();
        cout << "The " << i << "th token: " << tok->tag << endl;
    } while(tok->tag != END_OF_FILE);

    return 0;

}

提前感谢您的帮助! :D


编辑:

这是输入 Pascal 程序:

program Hello;
begin
  writeln ('Hello, world.');
  readln
end.

并澄清问题:

当符号表中的Token是原件的精确副本时,为什么从符号表中检索到的Token标签与放入符号表中的Token标签不同?

【问题讨论】:

  • tl;dr & 没有问题
  • @tobi303 问题是,程序token放入符号表时,正确的标签为46,但稍后检索时,标签为1207959598,尽管符号表中包含令牌的内存块与原始令牌本身相同。我想在帖子中更清楚地说明这个问题,什么是表达这个问题的好方法?
  • 添加适当的标签已经有助于避免像我这样的菜鸟批评;)(也许compiler-construction?)
  • 您添加到没有大小规范的 void 指针 - 我很确定它不应该编译。您使用的是什么编译器和标志?
  • @tobi303 我会添加标签,之前我不确定是否应该这样做,谢谢!

标签: c++ compiler-construction memcpy


【解决方案1】:

找到了。您正在用“H”覆盖标签的第一个字节。其他字节没问题。现在要找出 H 来自哪里...

nextLoc = nextLoc + sizeof(sizeType) + sizeof(unsigned char)
            + idSize*sizeof(char) + value.getSize();

您需要在此处再添加一个。您有跳过 (sizeType)、长度字节 (unsigned char)、id 本身 (idSize * sizeof(char)) 和值 (value.getSize()),但您还在 id 和 value 之间留下一个字节'不占。这就是为什么您的标签的最后一个字节被覆盖 - 并且因为您正在一个 little-endian 机器上进行测试,这会导致最高字节被损坏。

    for(int i = 0; i < *((unsigned char*)idSize); ++i){
     ...
        tbl_char_ptr = tbl_char_ptr + sizeof(char);
    ...
    }

    result_ptr = tbl_char_ptr + sizeof(char);

这比 idSize 多一个。

【讨论】:

  • SymbolTable 正在打印它在搜索中检查的每个令牌的位置,1428768 处的位置是前一个令牌的位置,而不是返回的 1428788。检查日志输出从插入而不是搜索显示,这是程序Token的位置。
  • 是的,对不起...我以为这就是您要问的问题。正如其他人所说,您的问题有点含糊。我现在正在尝试梳理数据可能被覆盖的位置...您是否也有输入文件?
  • 是的!我将添加输入文件,并尝试使问题更清晰。
  • 用实际错误更新了答案。编程风格使得阅读你正在做的事情变得非常困难。考虑将可变长度的东西放在最后并使用结构来写入它。此外,由于访问不对齐,此代码在许多计算机上非常容易出现总线错误。
  • 您是如何发现第一个字节被“H”覆盖的?
猜你喜欢
  • 2016-12-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多