【问题标题】:String argument value disappearing in call to class constructor, wiped by constructor of a member?字符串参数值在调用类构造函数时消失,被成员的构造函数擦除?
【发布时间】:2021-06-14 22:28:42
【问题描述】:

在我的项目中来自命令行参数的文件名上运行 stat() (perror(): "Bad address") 时,我得到了间歇性奇怪的结果:

// mz.h
class MzImage {
private:
    /* ... */
    struct Segment {
        Dword start, stop, length;
        std::string name, type;
        Segment(Dword start, Dword stop, Dword length, const std::string &name, const std::string &type) :
            start(start), stop(stop), length(length), name(name), type(type) {}
    };

    std::vector<Segment> segs_;

public:
    MzImage(const std::string &path);
    // ...
};

// mz.cpp
MzImage::MzImage(const std::string &path) {
    cout << "path = " << path << endl;
    // ... later, path.c_str() used in stat(), turned out to be NULL
}

// mztool.cpp
int main(int argc, char* argv[]) {
    const char* mzfile = argv[1];
    cout << "Filename: " << mzfile << endl;
    try {
        MzImage mz{string(mzfile)};
        // ...
    }
    catch (Error &e) {
        cout << "Exception: " << e.what() << endl;
    }
    return 0;
}

我注意到文件名参数在 main() 中很好,但在 MzImage 的构造函数中为空:

ninja@desktop:~/eaglestrike/mztools$ ./mztool a.exe
Filename: a.exe
path =

在调试器下,进入函数时字符串的值好像消失了:

ninja@desktop:~/eaglestrike/mztools$ gdb ./mztool
(gdb) b MzImage::MzImage
Breakpoint 1 at 0x6512: file mz.cpp, line 43.
(gdb) set args a.exe
(gdb) run
Starting program: /mnt/d/code/EagleStrike/mztools/mztool a.exe
Filename: a.exe

Breakpoint 1, MzImage::MzImage (this=0x7ffffffee100, path="a.exe") at mz.cpp:43
43      MzImage::MzImage(const std::string &path) {
(gdb) p path
$1 = "a.exe"
(gdb) n
44          cout << "path = " << path << endl;
(gdb) p path
$2 = ""

决定在字符串上加个watch,好像被vector构造函数抹杀了?

Breakpoint 1, MzImage::MzImage (this=0x7ffffffee100, path="a.exe") at mz.cpp:43
43      MzImage::MzImage(const std::string &path) {
(gdb) watch -l path
Watchpoint 3: -location path
(gdb) c
Continuing.

Watchpoint 3: -location path

Old value = "a.exe"
New value = <error reading variable: Cannot create a lazy string with address 0x0, and a non-zero length.>
0x000000000800bc8d in std::_Vector_base<MzImage::Segment, std::allocator<MzImage::Segment> >::_Vector_impl::_Vector_impl (this=0x7ffffffee138) at /usr/include/c++/7/bits/    stl_vector.h:89
89              : _Tp_alloc_type(), _M_start(), _M_finish(), _M_end_of_storage()
(gdb) bt
#0  0x000000000800bc8d in std::_Vector_base<MzImage::Segment, std::allocator<MzImage::Segment> >::_Vector_impl::_Vector_impl (this=0x7ffffffee138) at /usr/include/c++/7/bits/    stl_vector.h:89
#1  0x000000000800aba8 in std::_Vector_base<MzImage::Segment, std::allocator<MzImage::Segment> >::_Vector_base (this=0x7ffffffee138) at /usr/include/c++/7/bits/stl_vector.h:127
#2  0x000000000800a228 in std::vector<MzImage::Segment, std::allocator<MzImage::Segment> >::vector (this=0x7ffffffee138) at /usr/include/c++/7/bits/stl_vector.h:263
#3  0x0000000008006547 in MzImage::MzImage (this=0x7ffffffee100, path=<error reading variable: Cannot create a lazy string with address 0x0, and a non-zero length.>) at     mz.cpp:43
#4  0x0000000008005d19 in main (argc=2, argv=0x7ffffffee268) at mztool.cpp:22

对可能发生的事情有任何想法吗?这是在 WSL 下运行的 Ubuntu 18.04 上的 g++ 7.5.0。

【问题讨论】:

  • -Wshadow 添加到您的编译器选项中,然后修复阴影变量的问题。 (stopstartlengthnametype)...例如Segment(Dword _start, Dword _stop, ...,然后是start(_start), stop(_stop), ...
  • 如果我对这个程序进行一些微不足道的更改,以便它真正为我编译(添加using Dword = unsigned int;,用const std::exception&amp; 替换Exception&amp;,放入一些包含),我不能重现所描述的行为。当我将foo 作为命令行参数时,它会打印Filename: foopath = foo。您可以对其进行编辑以使其成为可重现的示例吗?
  • 无法重现:godbolt.org/z/s6shsrK9W
  • 很抱歉没有提供一个最小的例子,我自己在复制它时遇到了问题(见答案),感谢您抽出时间尝试复制它。

标签: c++


【解决方案1】:

很抱歉没有提供足够的信息,我自己无法随意重现该问题,因为它间歇性地出现。 这个问题原来是由 Makefile 搞砸引起的:

all: mztool

objs = mztool.o mz.o util.o
CC = g++
CXXFLAGS = -std=c++14 -g

mztool: $(objs)

我在 mz.h 中引入了更改,将成员添加到 MzImage,因此对象大小增加了。在运行make 之后,mz.o 被重建,但 mztool.o 没有,所以我最终链接了两个具有不同想法的 MzImage 对象的目标文件。 mztool.o 中main() 的代码在栈上创建了对象mz,但是大小太小了。后来,来自 mz.o 的代码运行了向量构造函数,它覆盖了堆栈上的字符串参数。哎哟。此外,每当我运行 make clean 或对 mztool.cpp 进行更改时,问题就会消失,因为这样 mztool.o 将使用更新的 MzImage 布局重新构建。

解决方案是在 Makefile 中引入适当的依赖关系,以便在 mz.h 更改时(以及其他)重新构建 mztool.o:

mztool.o: mz.cpp mz.h
mz.o: mz.cpp mz.h util.cpp util.h

【讨论】:

    【解决方案2】:

    主要问题是您的构造函数参数名称​​shadow您的成员变量名称。您需要为参数提供不同的名称,例如

    Segment(Dword _start, Dword _stop, Dword _length, 
        const std::string &_name, const std::string &_type) :
    start(_start), stop(_stop), length(_length), 
    name(_name), type(_type) {}
    

    除非您有单独的#define,否则对于Error,您需要将异常类型替换为const std::exception

    通过这些更改,您的代码可以正常编译(Unused argc 除外——您可以将 (void)argc; 放在 return 0; 之后)

    教训是始终在启用完整警告的情况下进行编译,并显式添加-Wshadow 以标记任何阴影变量(在这种情况下非常有用)。针对您的情况的一组好的警告可以是:

    g++ -Wall -Wextra -pedantic -Wshadow -std=c++17
    

    这将解决 99% 的问题。

    附加问题是您的评论中披露的问题:

    // ... later, path.c_str() used in stat(), turned out to be NULL
    

    其中.c_str() 是来自MzImage mz{string(mzfile)}; 的临时对象。这可能是您遇到问题的原因。 (感谢 Nathan Pierson)std::string::c_str() and temporaries 包含完整的解释。

    【讨论】:

    • 使用成员初始化列表不是意味着没有发生阴影吗?难道这个问题与临时取.c_str()有关吗?
    • 阴影仍在发生,例如warning: declaration of ‘type’ shadows a member of ‘MzImage::Segment’ [-Wshadow] - 这不会导致覆盖(警告),但它是一团糟。您可能在临时性上是正确的。
    • @DavidC.Rankin 我没有对你投反对票,但是 1) 阴影不会导致擦除不同类、不同堆栈帧中的变量值,以及 2) 没有理由你不能不要使用临时的c_str()(在其生命周期内),因为您链接到的答案有助于解释。
    • 是的,只有注释描述了.c_str() 的使用和“稍后”一词的使用,无法知道临时的生命周期是否已过期。事实证明这是一个make文件问题。
    猜你喜欢
    • 2011-12-07
    • 1970-01-01
    • 1970-01-01
    • 2014-11-21
    • 1970-01-01
    • 2011-03-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多