gcc、段错误和静态变量地址更改之谜(跨堆栈帧)
gcc, segfault, and the mystery of the changing address of a static variable (across stack frames)
我的应用程序出现了一个段错误,现在我已经研究了好几个小时了。我正在使用 gdb 分析回溯并注意到以下内容:
(gdb) frame 3
(gdb) info address C_STATIC_STRING
Symbol "C_STATIC_STRING" is static storage at address 0x66a660.
(gdb) frame 2
(gdb) info address C_STATIC_STRING
Symbol "C_STATIC_STRING" is static storage at address 0x66b800.
上面有 2 个堆栈帧在同一个头文件中引用相同的 const string C_STATIC_STRING
,但是一个帧正确地寻址了变量(帧 3)而另一个(帧 2)有一个偏移地址(通过如果我计算正确的话是 4512 字节)。
- 0x66a660 一个地址正确的字符串
- 如果读取 0x66b800 会导致错误:无法访问地址 0xffffffffffffffe8 处的内存
g++ (GCC) 4.8.5 20150623 (Red Hat 4.8.5-39.0.3)
附加信息:
我已经使用更简单的代码重现了这个问题:
- constants.h - 包含宏和常量
#ifndef CONSTANTS_H
#define CONSTANTS_H
using namespace std;
#include <iostream>
#include <string>
#ifndef C_MACRO
#define C_MACRO "MACRO "
#endif
const std::string CONSTANT = C_MACRO "CONSTANT_STRING";
#endif
- Test1 class - 有一个私有字符串,它在构造期间使用 CONSTANT
test1.h
#ifndef TEST1_H
#define TEST1_H
using namespace std;
#include <iostream>
#include <string>
#include "constants.h"
class Test1 {
public:
Test1();
std::string getString() {
return m_str;
}
private:
std::string m_str;
};
#endif
test1.cpp
using namespace std;
#include <iostream>
#include <string>
#include "test1.h"
Test1::Test1(): m_str(std::string("Extra ") + CONSTANT)
{
};
- Test class - 拥有 Test1 的一个实例
test.h
#ifndef TEST_H
#define TEST_H
using namespace std;
#include <iostream>
#include <string>
#include "test1.h"
#include "constants.h"
class Test {
public:
Test1 getTest() {
return m_test;
}
private:
Test1 m_test;
};
#endif
test.cpp - 几乎是空的
using namespace std;
#include <iostream>
#include <string>
#include "test.h"
- main.cpp -- 有一个静态的 Test 实例 class
using namespace std;
#include <iostream>
#include <string>
#include "test.h"
namespace NOTSTD {
Test variable;
}
using namespace NOTSTD;
int main()
{
std::cout << variable.getTest().getString() << " printed";
}
现在是构建过程
- 生成文件
#Test makefile
CPP = g++
CPPFLAGS = -Wall -ggdb -O0
AR = ar
RANLIB = ranlib
OUTPUT = test
all:: $(OUTPUT)
for_static = test1.o
static_lib.a: $(for_static)
$(AR) qc $@ $(for_static)
$(RANLIB) $@
$(OUTPUT): static_lib.a test.o main.o
$(CPP) ${CPPFLAGS} test.o main.o -o $(OUTPUT) static_lib.a
%.o : %.cpp
$(CPP) $(CPPFLAGS) -c $< -o $@
clean:
rm -f $(OUTPUT)
rm -f *.o
rm -f *.a
Test1 被编译成静态库,稍后用于编译其余部分。
在 Cygwin 中,它按预期工作
在 OEL 7 上出现分段错误(无论优化级别如何)
如果我省略静态链接库并只在 test1 中编译,那么它也适用于 OEL。
反汇编似乎表明问题在于静态 variables/constants.
的初始化顺序
我不太擅长 C++ 和编译器。也许有人知道到底发生了什么? GCC 错误还是只有我?
我想总结一下我从上面的有用评论中学到的东西:
- 静态变量在不同的地方有不同的地址是必然的Translation Units.
- 由于被称为Static Initialization Fiasco的现象,以我的方式使用静态变量,依赖于“好运”变量在使用前被初始化。如果在编译时运气不佳,您将在尝试使用变量时遇到段错误。
为了解决问题 2,我将我的静态变量包装在一个方法中(getter 之类的)并使用该方法而不是变量。它强制在正确的时间初始化其他静态变量。该方法看起来像这样:
Test getTest(){
static Test test;
return test;
}
感谢 David Schwartz 和 n.'pronouns' 的指导。
我的应用程序出现了一个段错误,现在我已经研究了好几个小时了。我正在使用 gdb 分析回溯并注意到以下内容:
(gdb) frame 3
(gdb) info address C_STATIC_STRING
Symbol "C_STATIC_STRING" is static storage at address 0x66a660.
(gdb) frame 2
(gdb) info address C_STATIC_STRING
Symbol "C_STATIC_STRING" is static storage at address 0x66b800.
上面有 2 个堆栈帧在同一个头文件中引用相同的 const string C_STATIC_STRING
,但是一个帧正确地寻址了变量(帧 3)而另一个(帧 2)有一个偏移地址(通过如果我计算正确的话是 4512 字节)。
- 0x66a660 一个地址正确的字符串
- 如果读取 0x66b800 会导致错误:无法访问地址 0xffffffffffffffe8 处的内存
g++ (GCC) 4.8.5 20150623 (Red Hat 4.8.5-39.0.3)
附加信息:
我已经使用更简单的代码重现了这个问题:
- constants.h - 包含宏和常量
#ifndef CONSTANTS_H
#define CONSTANTS_H
using namespace std;
#include <iostream>
#include <string>
#ifndef C_MACRO
#define C_MACRO "MACRO "
#endif
const std::string CONSTANT = C_MACRO "CONSTANT_STRING";
#endif
- Test1 class - 有一个私有字符串,它在构造期间使用 CONSTANT test1.h
#ifndef TEST1_H
#define TEST1_H
using namespace std;
#include <iostream>
#include <string>
#include "constants.h"
class Test1 {
public:
Test1();
std::string getString() {
return m_str;
}
private:
std::string m_str;
};
#endif
test1.cpp
using namespace std;
#include <iostream>
#include <string>
#include "test1.h"
Test1::Test1(): m_str(std::string("Extra ") + CONSTANT)
{
};
- Test class - 拥有 Test1 的一个实例 test.h
#ifndef TEST_H
#define TEST_H
using namespace std;
#include <iostream>
#include <string>
#include "test1.h"
#include "constants.h"
class Test {
public:
Test1 getTest() {
return m_test;
}
private:
Test1 m_test;
};
#endif
test.cpp - 几乎是空的
using namespace std;
#include <iostream>
#include <string>
#include "test.h"
- main.cpp -- 有一个静态的 Test 实例 class
using namespace std;
#include <iostream>
#include <string>
#include "test.h"
namespace NOTSTD {
Test variable;
}
using namespace NOTSTD;
int main()
{
std::cout << variable.getTest().getString() << " printed";
}
现在是构建过程
- 生成文件
#Test makefile
CPP = g++
CPPFLAGS = -Wall -ggdb -O0
AR = ar
RANLIB = ranlib
OUTPUT = test
all:: $(OUTPUT)
for_static = test1.o
static_lib.a: $(for_static)
$(AR) qc $@ $(for_static)
$(RANLIB) $@
$(OUTPUT): static_lib.a test.o main.o
$(CPP) ${CPPFLAGS} test.o main.o -o $(OUTPUT) static_lib.a
%.o : %.cpp
$(CPP) $(CPPFLAGS) -c $< -o $@
clean:
rm -f $(OUTPUT)
rm -f *.o
rm -f *.a
Test1 被编译成静态库,稍后用于编译其余部分。 在 Cygwin 中,它按预期工作 在 OEL 7 上出现分段错误(无论优化级别如何) 如果我省略静态链接库并只在 test1 中编译,那么它也适用于 OEL。
反汇编似乎表明问题在于静态 variables/constants.
的初始化顺序我不太擅长 C++ 和编译器。也许有人知道到底发生了什么? GCC 错误还是只有我?
我想总结一下我从上面的有用评论中学到的东西:
- 静态变量在不同的地方有不同的地址是必然的Translation Units.
- 由于被称为Static Initialization Fiasco的现象,以我的方式使用静态变量,依赖于“好运”变量在使用前被初始化。如果在编译时运气不佳,您将在尝试使用变量时遇到段错误。
为了解决问题 2,我将我的静态变量包装在一个方法中(getter 之类的)并使用该方法而不是变量。它强制在正确的时间初始化其他静态变量。该方法看起来像这样:
Test getTest(){
static Test test;
return test;
}
感谢 David Schwartz 和 n.'pronouns' 的指导。