bad_alloc 尝试打印分配给 $$ 结构成员的字符串时

bad_alloc when attempting to print string that was assigned to member of $$ struct

在我们的编译器的中间代码生成阶段,更具体地说,在测试算术表达式和赋值规则时,我注意到虽然相应的四边形构造成功,但有时在打印它们时我们会得到 bad_alloc 例外。跟踪之后,它看起来是由 printQuads() 方法引起的,特别是以下 key 的字符串访问:

if(q.result != nullptr && q.result->sym != nullptr) {
    cout << "quad " << opcodeStrings[q.op] << " inside if key check for" << opcodeStrings[q.op] << endl;
    resultKey = q.result->sym->key;
}

我将尝试包含相关代码,而不是在此处转储 500 行代码。 因此,您可以在下面看到我们的 assignmentexpr 和基本算术表达式规则和操作:

expr:                           assignexpr
                            |   expr PLUS expr
                                {
                                    bool isExpr1Arithm = check_arith();
                                    bool isExpr2Arithm = check_arith();
                                    if(!isExpr1Arithm || !isExpr2Arithm)
                                    {
                                        //string msg = !isExpr1Arithm ? "First operand isn\'t a number in addition!" : "Second operand isn\'t a number in addition!";
                                        yyerror(token_node, "Both addition operands must be numbers!");
                                    } else
                                    {
                                        double result = ->numConst + ->numConst;
                                        $$ = newexpr(arithmetic_e);
                                        $$->sym = newtemp(scope);
                                        $$->numConst = result;
                                        emit(add, , , $$, nextquadlabel(), yylineno);
                                    }
                                }
                            |   expr MIN expr
                                {
                                    bool isExpr1Arithm = check_arith();
                                    bool isExpr2Arithm = check_arith();
                                    if(!isExpr1Arithm || !isExpr2Arithm)
                                    {
                                        //string msg = !isExpr1Arithm ? "First operand isn\'t a number in subtraction!" : "Second operand isn\'t a number in subtracion!";
                                        yyerror(token_node, "Both suctraction operands must be numbers!");
                                    } else
                                    {
                                        double result = ->numConst - ->numConst;
                                        $$ = newexpr(arithmetic_e);
                                        $$->sym = newtemp(scope);
                                        $$->numConst = result;
                                        emit(sub, , , $$, nextquadlabel(), yylineno);
                                    }
                                }
                            |   expr MUL expr
                                {
                                    bool isExpr1Arithm = check_arith();
                                    bool isExpr2Arithm = check_arith();
                                    if(!isExpr1Arithm || !isExpr2Arithm)
                                    {
                                        //string msg = !isExpr1Arithm ? "First operand isn\'t a number in subtraction!" : "Second operand isn\'t a number in subtracion!";
                                        yyerror(token_node, "Both multiplication operands must be numbers!");
                                    } else
                                    {
                                        double result = ->numConst * ->numConst;
                                        $$ = newexpr(arithmetic_e);
                                        $$->sym = newtemp(scope);
                                        $$->numConst = result;
                                        emit(mul, , , $$, nextquadlabel(), yylineno);
                                    }
                                }
                            |   expr DIV expr
                                {
                                    bool isExpr1Arithm = check_arith();
                                    bool isExpr2Arithm = check_arith();
                                    if(!isExpr1Arithm || !isExpr2Arithm)
                                    {
                                        //string msg = !isExpr1Arithm ? "First operand isn\'t a number in subtraction!" : "Second operand isn\'t a number in subtracion!";
                                        yyerror(token_node, "Both division operands must be numbers!");
                                    } else
                                    {
                                        if(->numConst == 0) {
                                            yyerror(token_node, "division by 0!");
                                        } else {
                                            double result = ->numConst / ->numConst;
                                            $$ = newexpr(arithmetic_e);
                                            $$->sym = newtemp(scope);
                                            $$->numConst = result;
                                            emit(div_op, , , $$, nextquadlabel(), yylineno);
                                        }
                                    }
                                }
                            |   expr MOD expr
                                {
                                    bool isExpr1Arithm = check_arith();
                                    bool isExpr2Arithm = check_arith();
                                    if(!isExpr1Arithm || !isExpr2Arithm)
                                    {
                                        //string msg = !isExpr1Arithm ? "First operand isn\'t a number in subtraction!" : "Second operand isn\'t a number in subtracion!";
                                        yyerror(token_node, "Both modulus operands must be numbers!");
                                    } else
                                    {
                                        if(->numConst == 0) {
                                            yyerror(token_node, "division by 0!");
                                        } else {
                                            double result = fmod(->numConst,->numConst);
                                            $$ = newexpr(arithmetic_e);
                                            $$->sym = newtemp(scope);
                                            $$->numConst = result;
                                            emit(mod_op, , , $$, nextquadlabel(), yylineno);
                                        }
                                    }
                                }
...


assignexpr:                     lvalue ASSIGN expr  {   if ( isMemberOfFunc )
                                                        {
                                                            isMemberOfFunc=false;
                                                        }
                                                        else{   if ( islocalid==true ){
                                                                    islocalid = false;
                                                                }else{
                                                                    if ( isLibFunc(->sym->key) ) yyerror(token_node,"Library function \"" + ->sym->key + "\" is not lvalue!");
                                                                    if (SymTable_lookup(symtab,->sym->key,scope,false) && isFunc(->sym->key,scope)) yyerror(token_node,"User function \"" + ->sym->key + "\" is not lvalue!");
                                                                }
                                                        }
                                                        if(->type == tableitem_e)
                                                        {
                                                            // lvalue[index] = expr
                                                            emit(tablesetelem,->index,,,nextquadlabel(),yylineno);
                                                            $$ = emit_iftableitem(,nextquadlabel(),yylineno, scope);
                                                            $$->type = assignment;
                                                        } else
                                                        {
                                                            emit(assign,,NULL,,nextquadlabel(),yylineno); //lval = expr;
                                                            $$ = newexpr(assignment);
                                                            $$->sym = newtemp(scope);
                                                            emit(assign, ,NULL,$$,nextquadlabel(),yylineno);
                                                        }
                                                    }
                            ;

printQuads 方法如下:

void printQuads() {
unsigned int index = 1;
cout << "quad#\t\topcode\t\tresult\t\targ1\t\targ2\t\tlabel" <<endl;
cout << "-------------------------------------------------------------------------------------------------" << endl;
for(quad q : quads) {
    string arg1_type = "";
    string arg2_type = "";
    cout << "quad before arg1 type check" << endl;
    if(q.arg1 != nullptr) {
        switch (q.arg1->type) {
            case const_bool:
                arg1_type = "\'" + BoolToString(q.arg1->boolConst) + "\'";
                break;
            case const_string:
                arg1_type = "\"" + q.arg1->strConst + "\"";
                break;
            case const_num:
                arg1_type = to_string(q.arg1->numConst);
                break;
            case var:
                arg1_type = q.arg1->sym->key;
                break;
            case nil_e:
                arg1_type = "nil";
                break;
            default:
                arg1_type = q.arg1->sym->key;
                break;
        }
    }
    cout << "quad before arg2 type check" << endl;
    if(q.arg2 !=  nullptr) {
        switch (q.arg2->type) {
            case const_bool:
                arg2_type = "\'" + BoolToString(q.arg2->boolConst) + "\'";
                break;
            case const_string:
                arg2_type = "\"" + q.arg2->strConst + "\"";
                break;
            case const_num:
                arg2_type = to_string(q.arg2->numConst);
                break;
            case nil_e:
                arg2_type = "nil";
                break;
            default:
                arg2_type = q.arg2->sym->key;
                break;
        }
    }
    string label = "";
    if(q.op == if_eq || q.op == if_noteq || q.op == if_lesseq || q.op == if_greatereq
        || q.op == if_less || q.op == if_greater || q.op == jump) label = q.label;

    string resultKey = "";
    cout << "quad before key check" << endl;
    if(q.result != nullptr && q.result->sym != nullptr) {
        cout << "quad " << opcodeStrings[q.op] << " inside if key check for" << opcodeStrings[q.op] << endl;
        resultKey = q.result->sym->key;
    }
    cout << "quad after key check" << endl;
    cout << index << ":\t\t" << opcodeStrings[q.op] << "\t\t" << resultKey << "\t\t" << arg1_type << "\t\t" << arg2_type << "\t\t" << label << "\t\t" << endl;
    index++;
}
}

quads 变量只是一个四边形向量。这是四边形结构:

enum expr_t {
var,
tableitem_e,
user_func,
lib_func,
arithmetic_e,
assignment,
newtable_e,
const_num,
const_bool,
const_string,
nil_e,
bool_e
};

struct expr {
    expr_t type;
    binding* sym;
    expr* index;
    double numConst;
    string strConst;
    bool boolConst;
    expr* next;
};

struct quad {
    iopcode op;
    expr* result;
    expr* arg1;
    expr* arg2;
    unsigned int label;
    unsigned int line;
};

binding*定义如下,是一个符号table绑定:

enum SymbolType{GLOBAL_, LOCAL_, FORMAL_, USERFUNC_, LIBFUNC_, TEMP};

struct binding{
    std::string key;
    bool isactive = true;
    SymbolType sym;
    //vector<binding *> formals;
    scope_space space;
    unsigned int offset;
    unsigned int  scope;
    int line;
};

这是 emit()、newtemp 和 newexpr() 方法:

void emit(
        iopcode         op,
        expr*           arg1,
        expr*           arg2,
        expr*           result,
        unsigned int    label,
        unsigned int    line
    ){
    quad p;
    p.op            = op;
    p.arg1          = arg1;
    p.arg2          = arg2;
    p.result        = result;
    p.label         = label;
    p.line          = line;
    currQuad++;
    quads.push_back(p);
}

binding *newtemp(unsigned int scope){
    string name = newTempName();
    binding* sym = SymTable_get(symtab,name,scope);
    if (sym== nullptr){
        SymTable_put(symtab,name,scope,TEMP,-1);
        binding* sym =  SymTable_get(symtab,name,scope);
        return sym;
    }else return sym;
}

string newTempName(){
    string temp = "_t" + to_string(countertemp) + " ";
    countertemp++;
    return temp;
}

expr* newexpr(expr_t exprt){
    expr* current = new expr;
    current->sym = NULL;
    current->index = NULL;
    current->numConst = 0;
    current->strConst = "";
    current->boolConst = false;
    current->next = NULL;
    current->type = exprt;
    return current;
}

unsigned int countertemp = 0;
unsigned int currQuad = 0;

符号table cpp文件:

#include <algorithm>
bool isHidingBindings = false;

/* Return a hash code for pcKey.*/
static unsigned int SymTable_hash(string pcKey){
  size_t ui;
  unsigned int uiHash = 0U;
  for (ui = 0U; pcKey[ui] != '[=17=]'; ui++)
    uiHash = uiHash * HASH_MULTIPLIER + pcKey[ui];
  return (uiHash % DEFAULT_SIZE);
}

/*If b contains a binding with key pcKey, returns 1.Otherwise 0.
It is a checked runtime error for oSymTable and pcKey to be NULL.*/
int Bucket_contains(scope_bucket b, string pcKey){
    vector<binding> current = b.entries[SymTable_hash(pcKey)]; /*find the entry binding based on the argument pcKey*/
    for (int i=0; i<current.size(); i++){
        binding cur = current.at(i);
        if (cur.key==pcKey) return 1;
    }   
    return 0;
}

/*epistrefei to index gia to bucket pou antistixei sto scope 'scope'.Se periptwsh pou den uparxei
akoma bucket gia to en logw scope, ean to create einai true dhmiourgei to antistoixo bucket sto
oSymTable kai epistrefei to index tou.Diaforetika epistrefei thn timh -1.*/
int indexofscope(SymTable_T &oSymTable, unsigned int scope, bool create){
    int index=-1;
    for(int i=0; i<oSymTable.buckets.size(); i++) if (oSymTable.buckets[i].scope == scope) index=i;
    if ( index==-1 && create ){
        scope_bucket newbucket;
        newbucket.scope = scope;
        oSymTable.buckets.push_back(newbucket);
        index = oSymTable.buckets.size()-1;
    }
    return index;
}

/*If there is no binding with key : pcKey in oSymTable, puts a new binding with
this key and value : pvvValue returning 1.Otherise, it just returns 0.
It is a checked runtime error for oSymTable and pcKey to be NULL.*/
int SymTable_put(SymTable_T &oSymTable, string pcKey,unsigned int scope, SymbolType st, unsigned int line){
    int index = indexofscope(oSymTable,scope, true);
    if(index==-1) cerr<<"ERROR"<<endl;
    scope_bucket *current = &oSymTable.buckets.at(index);
    if ( Bucket_contains(*current, pcKey) && st != FORMAL_ && st != LOCAL_) return 0; /*If the binding exists in oSymTable return 0.*/
    binding newnode;
    newnode.key = pcKey;
    newnode.isactive = true;
    newnode.line =  line;
    newnode.sym = st;
    newnode.scope = scope;
    current->entries[SymTable_hash(pcKey)].push_back(newnode);
    return 1;
}

/*Pairnei ws orisma to oSymTable kai to scope pou theloume na apenergopoihsoume.
An to sugkekrimeno scope den uparxei sto oSymTable epistrefei -1.Diaforetika 0*/
void SymTable_hide(SymTable_T &oSymTable, unsigned int scope){
    isHidingBindings = true;
    for(int i=scope; i >= 0; i--) {
        if(i == 0) return;
        int index = indexofscope(oSymTable,i,false);
        if(index == -1) continue;
        scope_bucket *current = &oSymTable.buckets.at(index);
        for (int i=0; i<DEFAULT_SIZE; i++) {
            for (int j=0; j<current->entries[i].size(); j++) {
                if(current->entries[i].at(j).sym == LOCAL_ || current->entries[i].at(j).sym == FORMAL_) 
                    current->entries[i].at(j).isactive = false;
            }
        }
    }
}

void SymTable_show(SymTable_T &oSymTable, unsigned int scope){
    isHidingBindings = false;
    for(int i=scope; i >= 0; i--) {
        if(i == 0) return;
        int index = indexofscope(oSymTable,i,false);
         if(index == -1) continue;
        scope_bucket *current = &oSymTable.buckets.at(index);
        for (int i=0; i<DEFAULT_SIZE; i++) {
            for (int j=0; j<current->entries[i].size(); j++) {
                if(current->entries[i].at(j).sym == LOCAL_ || current->entries[i].at(j).sym == FORMAL_) 
                    current->entries[i].at(j).isactive = true;
            }
        }
    }
}

bool SymTable_lookup(SymTable_T oSymTable, string pcKey, unsigned int scope, bool searchInScopeOnly){
    for(int i=scope; i >= 0; i--) {
        if(searchInScopeOnly && i != scope) break;
        int index = indexofscope(oSymTable,i,false);
         if(index == -1) continue;
        scope_bucket current = oSymTable.buckets[index];
        for(vector<binding> entry : current.entries) {
            for(binding b : entry) {
                if(b.key == pcKey && b.isactive) return true;
                else if(b.key == pcKey && !b.isactive) return false;
            }
        }
    }
    return false;
}

binding* SymTable_lookupAndGet(SymTable_T &oSymTable, string pcKey, unsigned int scope) noexcept{
    for ( int i=scope; i >= 0; --i ){
        int index = indexofscope(oSymTable,i,false );
        if (index==-1) continue;
        scope_bucket &current = oSymTable.buckets[index];
        for (auto &entry : current.entries) {
            for (auto &b : entry ){
                if ( b.key == pcKey ) return &b;
            }
        }
    }
    return nullptr;
}

/*Lamvanei ws orisma to oSymTable, kleidh tou tou desmou pou psaxnoume kai to scope tou desmou.
H sunarthsh telika epistrefei to value tou tou desmou.Diaforetika epistrefei 0*/
binding* SymTable_get(SymTable_T &oSymTable, const string pcKey, unsigned int scope){
    for ( int i=scope; i >= 0; --i )
    {
        const int index = indexofscope( oSymTable, i, false );
        if ( index == -1 )
        {
            continue;
        }

        scope_bucket& current = oSymTable.buckets[index];

        for ( auto& entry : current.entries)
        {
            for ( auto& b : entry )
            {
                if ( b.key == pcKey )
                {
                    return &b;
                }
            }
        }
    }
    return nullptr;
}

当 运行 使用以下测试文件时,问题出现在 z5 = 4 / 2; 表达式的 assign quad:

// simple arithmetic operations
z1 = 1 + 2;
z10 = 1 + 1;
z2 = 1 - 3;
z3 = 4 * 4;
z4 = 5 / 2;

令人困惑的是,如果我在与算术相关的操作中的每个 emit() 之后打印出 sym->key,我就可以很好地看到键。但是一旦我尝试在 printQuads 中访问它们,它将失败(至少到目前为止 div 操作)。这让我想到,也许我们正在浅层复制 binding* sym 从而丢失密钥?但是怎么其他的都打印正常呢?

我认为这个问题(过去在不同阶段再次出现过)可能是由于我们使用大量按值复制而不是按引用引起的,但我不能完全确认这是因为大部分时间它都有效(我猜这意味着这是未定义的行为?)。

我敢肯定这很难帮助调试,但也许有人会在这么多小时后看到我看不到的东西。

通过观察代码进行调试可能是一项有用的技能,但它远非最有效的调试形式。现在,它的必要性要小得多,因为有很多好的工具可以用来检测问题。 (在这里,我的意思是“你”,特别是。 不能使用任何这些工具,因为我没有你的完整项目在我面前。我也不是特别想要它;这不是要求您粘贴数百行代码。

您几乎可以肯定您的问题与某种未定义的行为有关。如果您认为 bad_alloc 异常是由 std::string 的有效副本抛出的,那么很可能是由于 std::string 无效而被复制的结果.也许它是一个实际的 std::string 对象,其内部成员已被破坏;也许指针实际上并没有指向活动的 std::string (我认为这是真正的问题,见下文)。或者可能是其他原因。

无论哪种方式,错误都在错误出现之前很久就发生了,所以你只能靠运气偶然发现它发生的地方。另一方面,有多种内存错误检测工具可用,它们可能能够查明您通过读取或写入不属于您的内存而违反合同的准确时刻。其中包括 ValgrindAddressSanitizer(也称为 ASan);其中一个或两个当然可用于您开发项目的平台。 (我自信地说,即使不知道该平台是什么,但您必须做一些研究才能找到最适合您特定环境的平台。这两个名称都可以在维基百科上查找。)这些工具是非常容易使用,而且非常有用;它们可以为您节省数小时或数天的调试时间和很多挫折感。作为一个额外的好处,他们可以检测出错误你甚至不知道你有,让你免于发布一个会在客户或其他人手中爆炸的程序的尴尬谁在批改你的作业。所以我强烈建议学习如何使用它们。

我可能应该就此打住,因为这是学习使用这些工具的更好动力。尽管如此,我还是忍不住猜测问题出在哪里。但老实说,通过忽略我要说的内容并尝试自己找出问题,您会学到更多东西。

无论如何,你没有包含太多关于你的 SymTable_T class 的信息,不一致的命名约定让我怀疑你是否编写了它的代码;也许它是您为此任务提供的骨架代码的一部分。从我在 SymTable_putSymTable_get 中看到的情况来看,SymTable_T 包含类似于散列 table 的内容,但不使用 C++ 标准库关联容器。 (恕我直言,这从一开始就是一个错误。这项作业是关于学习如何生成代码,而不是如何编写好的哈希 table。C++ 标准库关联容器当然足以满足您的目的,无论它们是否是您用例的绝对理想选择,并且它们具有已经被彻底记录和调试的巨大优势。)

可能 SymTable_T 最初根本不是用 C++ 编写的。 free-standing 函数(如 SymTable_putSymTable_get 而不是 class 方法的使用很难解释,除非这些函数最初是用 C 语言编写的,而 C 语言不允许使用对象方法。另一方面,它们似乎使用 C++ 标准库集合,正如 SymTable_put:

中对 push_back 的调用所证明的那样
current->entries[SymTable_hash(pcKey)].push_back(newnode);

这表明 entries 是一个 std::vector(尽管还有其他可能性),如果是,当您将它与此结合时,它应该会发出危险信号,来自 SymTable_get(whitespace-edited 在此处保存屏幕 space):

for ( auto& entry : current.entries) {
    for ( auto& b : entry ) {
        if ( b.key == pcKey )
            return &b;
    }
}

老实说,我不明白那个双循环。首先,您似乎忽略了该数据结构中某处存在散列 table 的事实,但除此之外,在我看来 entry 应该是 binding (这就是 SymTable_put 推入 entries 容器的内容),我看不出 binding 在哪里是可迭代对象。可能我没看对。)

无论如何,显然 SymTable_get 正在返回对存储在容器中的内容的引用,可能是 std::vector,并且该容器会不时通过将新元素推送到其中进行修改.并且 将一个新元素推到 std::vector 的末尾会使对 vector 的每个元素的所有现有引用无效。 (参见 https://en.cppreference.com/w/cpp/container/vector/push_back

因此,newtemp,returns 从 SymTable_get 获取的 binding*,正在返回一个指针,该指针将来可能会因对 [= 的某些调用而失效20=](虽然不是每次调用该函数;只有那些星星不幸排成一排的函数)。然后将该指针存储到一个数据对象中,该对象将(很久以后)提供给 printQuads,它将尝试se 指针以复制它将尝试打印的字符串。而且,正如我在本文开头提到的,尝试使用悬垂指针指向的对象是未定义的行为。

需要注意的是,复制一个字符串来打印它是完全没有必要的。一个引用就可以很好地工作,并且可以节省一堆不必要的内存分配。但这并不能解决问题(如果我的猜测被证明是正确的),因为通过悬空指针打印与通过悬空指针制作副本一样是未定义行为,并且可能会以其他神秘方式显现。