【问题标题】:Partial line from cpp file ending up in output file - haunted code?cpp 文件中的部分行以输出文件结尾 - 闹鬼的代码?
【发布时间】:2013-01-27 20:59:11
【问题描述】:

很抱歉,要制作一个完全可重现的错误版本非常困难 --- 所以请使用我的原理图代码。

此程序从网页检索信息,对其进行处理,并将输出保存到 ASCII 文件中。我还有一个“日志”文件(FILE *theLog---包含在 Manager 对象中)用于报告错误等。


一些后台方法:

// Prints string to log file
void Manager::logEntry(const string lstr) {
    if( theLog != NULL ) { fprintf(theLog, "%s", lstr.c_str()); }
}

// Checks if file with given name already exists
bool fileExists(const string fname) {
    FILE *temp;
    if( temp = fopen(fname.c_str(), "r") ) { 
        fclose(temp);
        return true;
    } else { return false; }
}

// Initialize file for writing (some components omitted)...
bool initFile(FILE *&oFile, const string fname) {
    if(oFile = fopen(fname.c_str(), "w") ) { return true; }
    else { return false; }
}

惹麻烦的东西:

// Gets data from URL, saves to file 'dataFileName', input control flag 'foreCon'
//                     stu is some object that has string which i want
bool saveData(Manager *man, Stuff *stu, string dataFileName, const int foreCon) {
    char logStr[CHARLIMIT_LARGE];          // CHARLIMIT_LARGE = 2048
    sprintf(logStr, "Saving Data...\n");
    man->logEntry( string(logStr) );       // This appears fine in 'theLog' correctly

    string data = stu->getDataPrefixStr() + getDataFromURL() + "\n";        // fills 'data' with stuff
    data += stu->getDataSuffixStr();

    if( fileExists(dataFileName) ) {
        sprintf(logStr, "save file '%s' already exists.", dataFileName.c_str() );
        man->logEntry( string(logStr) );
        if( foreCon == -1 ) {
            sprintf(logStr, "foreCon = %d, ... exiting.", foreCon);        // LINE 'A' : THIS LINE ENDS UP IN OUTPUT FILE
            tCase->logEntry( string(logStr) );
            return false;
        } else {
            sprintf(logStr, "foreCon = %d, overwriting file.", foreCon);   // LINE 'B' : THIS LINE ENDS UP IN LOG FILE
            tCase->logEntry( string(logStr) );
        }                                                                                                 
    }

    // Initialize output file
    FILE *outFile;
    if( !initFile(outFile, dataFileName) ) {
        sprintf(logStr, "couldn't initFile '%s'", dataFileName.c_str());
        tCase->logEntry( string(logStr) );
        return false;
    }

    fprintf(outFile, "%s", data.c_str());                 // print data to output file

    if( fclose(outFile) != EOF) {
        sprintf(logStr, "saved to '%s'", dataFileName.c_str());
        tCase->logEntry( string(logStr) );
        return true;
    }

    return false;
}

如果文件已经存在,并且“int foreCon = -1”,那么代码应该将“A”行打印到日志文件中。如果文件存在且foreCon != -1,则旧文件将被data 覆盖。如果文件不存在,则创建该文件并将数据写入其中。

然而,结果是数据文件中出现了行'A'的分解版本,并且日志文件中打印了行'B'!!!

数据文件的样子:

.. exiting.20130127 161456
20130127 000000,55,17,11,0.00
20130127 010000,54,17,11,0.00
... ...

第二行及以后的行看起来正确,但有一行包含“A”行的一部分。

现在,真正奇怪的部分。如果我注释掉 if( foreCon == -1) { ... } 块中的所有内容,则数据文件如下所示:

%d, ... exiting.20130127 161456
20130127 000000,55,17,11,0.00
20130127 010000,54,17,11,0.00
... ...

还有一行多余的,不过是复制到数据文件中的LITERAL CODE。

我认为我的代码中有一个恶作剧。我不明白这怎么会发生。


编辑:我尝试打印以控制台 data 字符串,它给出了相同的混乱值:即 %d, ... exiting.20130127 161456 - 所以它一定是关于 string 而不是 FILE *

【问题讨论】:

  • 你编译你的代码包括调试符号吗?在我看来,某处缺少 \0。
  • 可怕...在你知道之前,它会拿着刀追上你。
  • "this is a string" 这样的硬编码字符串(在大多数系统中)存储在可执行文件的一个特殊部分中,称为“数据段”,然后将其复制或映射到内存中。这就是代码如何通过编译语言进入运行时内存。
  • @zhermes 你不能像那样添加字符和字符串。您将 35(“#”的 ASCII 码)添加到“file created on ...”的地址,即 getDataPrefixStr() 是从该字符串开头的 35 个字符开始的任何内容。
  • @molbdnilo 刚刚发现的问题应该会导致编译器警告,如果你让它们一直向上。 (类似于 “表达式使指针从整数不强制转换” 或相反。)

标签: c++ c file printf


【解决方案1】:

根据您的最新评论回答:

getDataPrefixStr() 最终返回一个以开头的字符串 类似于 string retStr = COMCHAR + " file created on ...";这样的 那 const char COMCHAR = '#';。 COMCHAR 可能是问题吗??

您不能像这样添加字符和字符串文字(它们是char 的数组,而不是strings)。

您将 35(“#”的 ASCII 码)添加到“file created on ...”的地址,即 getDataPrefixStr() 是从该字符串开头 35 个字符开始的任何内容。由于所有文字字符串都存储在同一个数据区域中,因此您将在输出中从程序中获取字符串。

相反,你冷做

   const string COMCHAR = "*"; 
   string retStr = COMCHAR + " file created on ...";

【讨论】:

    【解决方案2】:

    可能是logStr 太短,导致其他缓冲区中的数据被覆盖(您是否仔细检查了CHARLIMIT_LARGE?)。您可以通过评论对logStr (sprintf) 的所有写入来诊断此问题,并查看data 是否仍然损坏。一般来说,如果用户可以设置dataFileName(是一个很长的字符串),您的代码很容易受到攻击;请改用snprintfostringstream

    否则,我猜stu->getDataPrefixStr()getDataFromURL() 将返回损坏的结果或返回类型char* 而不是string。尝试将这些值直接打印到控制台以查看它们是否已损坏。如果它们返回 char*,则 data = stu->getDataPrefixStr() + getDataFromURL() 将具有未定义的行为。

    【讨论】:

      【解决方案3】:
      if( temp = fopen(fname.c_str(), 'r') ) { 
      

      应该是

      if( temp = fopen(fname.c_str(), "r") ) { 
      

      【讨论】:

      • 我会说这样的错误会导致未定义的行为,然后它可能就是答案。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-03
      • 1970-01-01
      相关资源
      最近更新 更多