您似乎认为“真实字符串文字”的必要特征是编译器将其嵌入可执行文件的静态存储中。
事实并非如此。C和C++标准保证字符串文字具有静态存储持续时间,因此它必须存在于程序的生命周期内,但是如果编译器可以安排这一点而不将文字放在静态存储中,则可以自由地这样做,有些编译器有时会这样做。
然而,显然你想要测试的属性对于给定的字符串文字是是否实际上在静态存储中。由于根据语言标准,它不需要在静态存储中,因此不能仅仅基于可移植的C/C++解决你的问题。
一个给定字符串字面值实际上是否在静态存储中,是指该字符串字面值的地址是否位于分配给链接节的地址范围之一,并且这些链接节符合所用工具链构建程序时的静态存储术语。
因此我建议的解决方案是使您的程序能够了解那些符合静态存储的链接段的地址范围,然后它就可以通过显然的代码测试给定的字符串文字是否在静态存储中。
这里是一个玩具C++项目prog
的示例,使用GNU/Linux x86_64工具链构建(C++98或更高版本可以,对于C而言稍微麻烦一些)。在此设置中,我们以ELF格式链接,并且我们将认为链接节 .bss
(0初始化的静态数据),.rodata
(只读静态)和 .data
(读/写静态数据)是“静态存储”。
以下是我们的源文件:
section_bounds.h
// Export delimiting values for our `.bss`, `.rodata` and `.data` sections
extern unsigned long const section_bss_start;
extern unsigned long const section_bss_size;
extern unsigned long const section_bss_end;
extern unsigned long const section_rodata_start;
extern unsigned long const section_rodata_size;
extern unsigned long const section_rodata_end;
extern unsigned long const section_data_start;
extern unsigned long const section_data_size;
extern unsigned long const section_data_end;
#ifndef BSS_START
#define BSS_START 0x0
#endif
#ifndef BSS_SIZE
#define BSS_SIZE 0xffff
#endif
#ifndef RODATA_START
#define RODATA_START 0x0
#endif
#ifndef RODATA_SIZE
#define RODATA_SIZE 0xffff
#endif
#ifndef DATA_START
#define DATA_START 0x0
#endif
#ifndef DATA_SIZE
#define DATA_SIZE 0xffff
#endif
extern unsigned long const
section_bss_start = BSS_START;
extern unsigned long const section_bss_size = BSS_SIZE;
extern unsigned long const
section_bss_end = section_bss_start + section_bss_size;
extern unsigned long const
section_rodata_start = RODATA_START;
extern unsigned long const
section_rodata_size = RODATA_SIZE;
extern unsigned long const
section_rodata_end = section_rodata_start + section_rodata_size;
extern unsigned long const
section_data_start = DATA_START;
extern unsigned long const
section_data_size = DATA_SIZE;
extern unsigned long const
section_data_end = section_data_start + section_data_size;
cstr_storage_triage.h
:这是一个文件名,可能与C++字符串存储有关。
#ifndef CSTR_STORAGE_TRIAGE_H
#define CSTR_STORAGE_TRIAGE_H
extern void cstr_storage_triage(const char *s);
#endif
cstr_storage_triage.cpp
#include "cstr_storage_triage.h"
#include "section_bounds.h"
#include <iostream>
using namespace std;
void cstr_storage_triage(const char *s)
{
unsigned long addr = (unsigned long)s;
cout << "When s = " << (void*)s << " -> \"" << s << '\"' << endl;
if (addr >= section_bss_start && addr < section_bss_end) {
cout << "then s is in static 0-initialized data\n";
} else if (addr >= section_rodata_start && addr < section_rodata_end) {
cout << "then s is in static read-only data\n";
} else if (addr >= section_data_start && addr < section_data_end){
cout << "then s is in static read/write data\n";
} else {
cout << "then s is on the stack/heap\n";
}
}
main.cpp
#include "cstr_storage_triage.h"
static char in_bss[1];
static char const * in_rodata = "In static read-only data";
static char in_rwdata[] = "In static read/write data";
int main()
{
char on_stack[] = "On stack";
cstr_storage_triage(in_bss);
cstr_storage_triage(in_rodata);
cstr_storage_triage(in_rwdata);
cstr_storage_triage(on_stack);
cstr_storage_triage("Where am I?");
return 0;
}
这是我们的makefile:
.PHONY: all clean
SRCS = main.cpp cstr_storage_triage.cpp section_bounds.cpp
OBJS = $(SRCS:.cpp=.o)
TARG = prog
MAP_FILE = $(TARG).map
ifdef AGAIN
BSS_BOUNDS := $(shell grep -m 1 '^\.bss ' $(MAP_FILE))
BSS_START := $(word 2,$(BSS_BOUNDS))
BSS_SIZE := $(word 3,$(BSS_BOUNDS))
RODATA_BOUNDS := $(shell grep -m 1 '^\.rodata ' $(MAP_FILE))
RODATA_START := $(word 2,$(RODATA_BOUNDS))
RODATA_SIZE := $(word 3,$(RODATA_BOUNDS))
DATA_BOUNDS := $(shell grep -m 1 '^\.data ' $(MAP_FILE))
DATA_START := $(word 2,$(DATA_BOUNDS))
DATA_SIZE := $(word 3,$(DATA_BOUNDS))
CPPFLAGS += \
-DBSS_START=$(BSS_START) \
-DBSS_SIZE=$(BSS_SIZE) \
-DRODATA_START=$(RODATA_START) \
-DRODATA_SIZE=$(RODATA_SIZE) \
-DDATA_START=$(DATA_START) \
-DDATA_SIZE=$(DATA_SIZE)
endif
all: $(TARG)
clean:
rm -f $(OBJS) $(MAP_FILE) $(TARG)
ifndef AGAIN
$(MAP_FILE): $(OBJS)
g++ -o $(TARG) $(CXXFLAGS) -Wl,-Map=$@ $(OBJS) $(LDLIBS)
touch section_bounds.cpp
$(TARG): $(MAP_FILE)
$(MAKE) AGAIN=1
else
$(TARG): $(OBJS)
g++ -o $@ $(CXXFLAGS) $(OBJS) $(LDLIBS)
endif
下面是make
的样子:
$ make
g++ -c -o main.o main.cpp
g++ -c -o cstr_storage_triage.o cstr_storage_triage.cpp
g++ -c -o section_bounds.o section_bounds.cpp
g++ -o prog -Wl,-Map=prog.map main.o cstr_storage_triage.o section_bounds.o
touch section_bounds.cpp
make AGAIN=1
make[1]: Entering directory `/home/imk/develop/SO/string_lit_only'
g++ -DBSS_START=0x00000000006020c0 -DBSS_SIZE=0x118 -DRODATA_START=0x0000000000400bf0
-DRODATA_SIZE=0x120 -DDATA_START=0x0000000000602070 -DDATA_SIZE=0x3a
-c -o section_bounds.o section_bounds.cpp
g++ -o prog main.o cstr_storage_triage.o section_bounds.o
最后,prog
做什么:
$ ./prog
When s = 0x6021d1 -> ""
then s is in static 0-initialized data
When s = 0x400bf4 -> "In static read-only data"
then s is in static read-only data
When s = 0x602090 -> "In static read/write data"
then s is in static read/write data
When s = 0x7fffa1b053a0 -> "On stack"
then s is on the stack/heap
When s = 0x400c0d -> "Where am I?"
then s is in static read-only data
如果你已经知道它是如何工作的,那么你就不需要继续阅读了。
即使在我们不知道静态存储区域的地址和大小的情况下,程序仍然可以编译和链接。毕竟,这是必须的,不是吗?在这种情况下,应该保存这些值的全局section_*
变量都会使用占位符值构建。
当运行make
命令时,执行以下操作:
$(TARG): $(MAP_FILE)
$(MAKE) AGAIN=1
和
$(MAP_FILE): $(OBJS)
g++ -o $(TARG) $(CXXFLAGS) -Wl,-Map=$@ $(OBJS) $(LDLIBS)
touch section_bounds.cpp
由于未定义 AGAIN
,这些操作是有效的。它们告诉 make
,为了构建 prog
,必须首先按照第二个配方构建 prog
的链接器映射文件,然后重新为 section_bounds.cpp
设置时间戳。在此之后,make
将再次调用自身,并定义 AGAIN
= 1。
再次执行带有定义好的 AGAIN
的 makefile,make
现在发现必须计算所有变量:
BSS_BOUNDS
BSS_START
BSS_SIZE
RODATA_BOUNDS
RODATA_START
RODATA_SIZE
DATA_BOUNDS
DATA_START
DATA_SIZE
对于每个静态存储段
S
,它通过在链接器映射文件中查找报告
S
地址和大小的行来计算
S_BOUNDS
。从该行开始,将第二个单词(=部分地址)分配给
S_START
,将第三个单词(=部分大小)分配给
S_SIZE
。然后,所有分隔符值都通过
-D
选项附加到
CPPFLAGS
上,这些选项将自动传递给编译过程。因为定义了
AGAIN
,所以
$(TARG)
的操作性方案现在是惯例:
$(TARG): $(OBJS)
g++ -o $@ $(CXXFLAGS) $(OBJS) $(LDLIBS)
但是我们在父级make
中涉及到了section_bounds.cpp
文件;因此它必须重新编译,进而需要重新链接prog
。这次编译section_bounds.cpp
时,会编译所有的分节定界宏:
BSS_START
BSS_SIZE
RODATA_START
RODATA_SIZE
DATA_START
DATA_SIZE
这些值将具有预定义的值,并且不会假设它们的占位符值。
那些预定义的值是正确的,因为第二个连接没有向链接添加任何符号,也没有删除符号,并且不会更改任何符号的大小或存储类。它只是为在第一个链接中存在的符号分配不同的值。因此,静态存储部分的地址和大小将不会被更改,并且现在已知道它们对于您的程序是什么。