区分字符串文字和字符数组
Differentiate String Literal from Char Array
我想编写一些接受字符串文字的函数 - 仅一个字符串文字:
template <size_t N>
void foo(const char (&str)[N]);
不幸的是,这太宽泛了,将匹配 char
的任何数组 - 无论它是否是真正的字符串文字。虽然不可能在编译时分辨出它们之间的区别 - without 不得不求助于要求 caller 包装 literal/array - 在 运行 时间,两个数组将位于内存中完全不同的位置:
foo("Hello"); // at 0x400f81
const char msg[] = {'1', '2', '3'};
foo(msg); // at 0x7fff3552767f
有没有办法知道字符串数据在内存中的位置,这样我至少可以 assert
该函数只接受字符串文字? (使用 gcc 4.7.3,但真正适用于任何编译器的解决方案都很棒)。
你似乎认为 "true string literal" 的必要特征
是编译器将其烘焙到可执行文件的静态存储中。
事实并非如此。 C 和 C++ 标准向我们保证
字符串文字应具有静态存储 duration,因此它必须存在于
程序的生命周期,但如果编译器可以在不放置
静态存储中的文字,这样做是自由的,有些编译器有时
做。
但是,对于给定的字符串,很明显您要测试的 属性
字面量,是它实际上是否在静态存储中。因为它 不需要
在静态存储中,只要语言标准保证,就有
不能仅基于便携式 C/C++.
解决您的问题
问题是给定的字符串文字实际上是否在静态存储中
字符串文字的地址是否位于其中一个
分配给符合条件的 linkage 部分的地址范围
静态存储,在您的特定工具链的命名法中,当
您的程序是由该工具链构建的。
所以我建议的解决方案是让您的程序知道
它自己的 linkage 部分的地址范围
静态存储,然后可以测试给定的字符串字面量是否
通过明显的代码在静态存储中。
这是玩具 C++ 项目的此解决方案的示例,prog
使用 GNU/Linux x86_64 工具链构建(C++98 或更高版本即可,并且
对于 C) 来说,这种方法只是稍微复杂一点。在这个设置中,我们 link 在 ELF
格式,我们认为 静态存储 的 linkage 部分是 .bss
(0 初始化的静态数据),.rodata
(只读静态静态)和.data
(read/write静态数据)。
这是我们的源文件:
section_bounds.h
#ifndef SECTION_BOUNDS_H
#define 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;
#endif
section_bounds.cpp
// Assign either placeholder or pre-defined values to
// the section delimiting globals.
#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
#ifndef CSTR_STORAGE_TRIAGE_H
#define CSTR_STORAGE_TRIAGE_H
// Classify the storage type addressed by `s` and print it on `cout`
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
// Demonstrate storage classification of various arrays of char
#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
如果很明显这是如何工作的,则无需进一步阅读。
程序将编译 link 甚至在我们知道地址和
其静态存储部分的大小。它也需要,不是吗!?在
在这种情况下,应该保存这些值的全局 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
的 linker 映射文件,按照
第二个食谱,然后重新添加时间戳section_bounds.cpp
。在那之后,
make
是再次调用自己,AGAIN
定义=1.
再次执行生成文件,定义了 AGAIN
,make
现在发现它
必须计算所有变量:
BSS_BOUNDS
BSS_START
BSS_SIZE
RODATA_BOUNDS
RODATA_START
RODATA_SIZE
DATA_BOUNDS
DATA_START
DATA_SIZE
对于每个静态存储部分 S
,它通过 grepping 计算 S_BOUNDS
报告 S
地址和大小的行的 linker 映射文件。
从该行开始,它将第二个字(= 节地址)分配给 S_START
,
第三个字(= 部分的大小)到 S_SIZE
。所有的部分
然后通过 -D
选项将定界值附加到 CPPFLAGS
将自动传递给编译。
因为定义了AGAIN
,$(TARG)
的有效配方现在是惯例:
$(TARG): $(OBJS)
g++ -o $@ $(CXXFLAGS) $(OBJS) $(LDLIBS)
但是我们在父make
中触及了section_bounds.cpp
;所以它必须是
重新编译,因此 prog
必须重新link。这一次,当
section_bounds.cpp
被编译,所有的段分隔宏:
BSS_START
BSS_SIZE
RODATA_START
RODATA_SIZE
DATA_START
DATA_SIZE
将具有预定义的值,并且不会采用它们的占位符值。
那些预定义的值将是正确的,因为第二个 linkage
不向 linkage 添加任何符号并删除 none,并且不改变
任何符号的大小或存储 class。它只是分配不同的值
第一个 link 时代出现的符号。因此,
静态存储部分的地址和大小将保持不变,并且现在为您的程序所知。
您可以使用用户定义的文字,根据定义只能应用于文字:
#include <iostream>
struct literal_wrapper
{
const char* const ptr;
private:
constexpr literal_wrapper(const char* p) : ptr(p) {}
friend constexpr literal_wrapper operator "" _lw(const char* p, std::size_t);
};
constexpr literal_wrapper operator "" _lw(const char* p, std::size_t){ return literal_wrapper(p); }
literal_wrapper f()
{
std::cout << "f()" << std::endl;
return "test"_lw;
}
void foo(const literal_wrapper& lw)
{
std::cout << "foo:" << lw.ptr << " " << static_cast<const void*>(lw.ptr) << std::endl;
}
int main()
{
auto x1 = f(), x2 = f(), x3 = f();
const void* p1 = x1.ptr;
const void* p2 = x2.ptr;
const void* p3 = x3.ptr;
std::cout << x1.ptr << " " << p1 << " " << p2 << " " << p3 << std::endl;
foo(x1);
foo(x2);
foo("test"_lw);
foo("test2"_lw);
}
根据您的具体需求,这可能适合您,也可能不适合您:
#include <cstdlib>
template <size_t N>
void foo(const char (&str)[N]) {}
template <char> struct check_literal {};
#define foo(arg) foo((check_literal<arg[0]>(),arg))
int main()
{
// This compiles
foo("abc");
// This does not
static const char abc[] = "abc";
foo(abc);
}
这仅适用于 -std=c++11
模式下的 g++ 和 clang++。
我想编写一些接受字符串文字的函数 - 仅一个字符串文字:
template <size_t N>
void foo(const char (&str)[N]);
不幸的是,这太宽泛了,将匹配 char
的任何数组 - 无论它是否是真正的字符串文字。虽然不可能在编译时分辨出它们之间的区别 - without 不得不求助于要求 caller 包装 literal/array - 在 运行 时间,两个数组将位于内存中完全不同的位置:
foo("Hello"); // at 0x400f81
const char msg[] = {'1', '2', '3'};
foo(msg); // at 0x7fff3552767f
有没有办法知道字符串数据在内存中的位置,这样我至少可以 assert
该函数只接受字符串文字? (使用 gcc 4.7.3,但真正适用于任何编译器的解决方案都很棒)。
你似乎认为 "true string literal" 的必要特征 是编译器将其烘焙到可执行文件的静态存储中。
事实并非如此。 C 和 C++ 标准向我们保证 字符串文字应具有静态存储 duration,因此它必须存在于 程序的生命周期,但如果编译器可以在不放置 静态存储中的文字,这样做是自由的,有些编译器有时 做。
但是,对于给定的字符串,很明显您要测试的 属性 字面量,是它实际上是否在静态存储中。因为它 不需要 在静态存储中,只要语言标准保证,就有 不能仅基于便携式 C/C++.
解决您的问题问题是给定的字符串文字实际上是否在静态存储中 字符串文字的地址是否位于其中一个 分配给符合条件的 linkage 部分的地址范围 静态存储,在您的特定工具链的命名法中,当 您的程序是由该工具链构建的。
所以我建议的解决方案是让您的程序知道 它自己的 linkage 部分的地址范围 静态存储,然后可以测试给定的字符串字面量是否 通过明显的代码在静态存储中。
这是玩具 C++ 项目的此解决方案的示例,prog
使用 GNU/Linux x86_64 工具链构建(C++98 或更高版本即可,并且
对于 C) 来说,这种方法只是稍微复杂一点。在这个设置中,我们 link 在 ELF
格式,我们认为 静态存储 的 linkage 部分是 .bss
(0 初始化的静态数据),.rodata
(只读静态静态)和.data
(read/write静态数据)。
这是我们的源文件:
section_bounds.h
#ifndef SECTION_BOUNDS_H
#define 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;
#endif
section_bounds.cpp
// Assign either placeholder or pre-defined values to
// the section delimiting globals.
#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
#ifndef CSTR_STORAGE_TRIAGE_H
#define CSTR_STORAGE_TRIAGE_H
// Classify the storage type addressed by `s` and print it on `cout`
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
// Demonstrate storage classification of various arrays of char
#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
如果很明显这是如何工作的,则无需进一步阅读。
程序将编译 link 甚至在我们知道地址和
其静态存储部分的大小。它也需要,不是吗!?在
在这种情况下,应该保存这些值的全局 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
的 linker 映射文件,按照
第二个食谱,然后重新添加时间戳section_bounds.cpp
。在那之后,
make
是再次调用自己,AGAIN
定义=1.
再次执行生成文件,定义了 AGAIN
,make
现在发现它
必须计算所有变量:
BSS_BOUNDS
BSS_START
BSS_SIZE
RODATA_BOUNDS
RODATA_START
RODATA_SIZE
DATA_BOUNDS
DATA_START
DATA_SIZE
对于每个静态存储部分 S
,它通过 grepping 计算 S_BOUNDS
报告 S
地址和大小的行的 linker 映射文件。
从该行开始,它将第二个字(= 节地址)分配给 S_START
,
第三个字(= 部分的大小)到 S_SIZE
。所有的部分
然后通过 -D
选项将定界值附加到 CPPFLAGS
将自动传递给编译。
因为定义了AGAIN
,$(TARG)
的有效配方现在是惯例:
$(TARG): $(OBJS)
g++ -o $@ $(CXXFLAGS) $(OBJS) $(LDLIBS)
但是我们在父make
中触及了section_bounds.cpp
;所以它必须是
重新编译,因此 prog
必须重新link。这一次,当
section_bounds.cpp
被编译,所有的段分隔宏:
BSS_START
BSS_SIZE
RODATA_START
RODATA_SIZE
DATA_START
DATA_SIZE
将具有预定义的值,并且不会采用它们的占位符值。
那些预定义的值将是正确的,因为第二个 linkage 不向 linkage 添加任何符号并删除 none,并且不改变 任何符号的大小或存储 class。它只是分配不同的值 第一个 link 时代出现的符号。因此, 静态存储部分的地址和大小将保持不变,并且现在为您的程序所知。
您可以使用用户定义的文字,根据定义只能应用于文字:
#include <iostream>
struct literal_wrapper
{
const char* const ptr;
private:
constexpr literal_wrapper(const char* p) : ptr(p) {}
friend constexpr literal_wrapper operator "" _lw(const char* p, std::size_t);
};
constexpr literal_wrapper operator "" _lw(const char* p, std::size_t){ return literal_wrapper(p); }
literal_wrapper f()
{
std::cout << "f()" << std::endl;
return "test"_lw;
}
void foo(const literal_wrapper& lw)
{
std::cout << "foo:" << lw.ptr << " " << static_cast<const void*>(lw.ptr) << std::endl;
}
int main()
{
auto x1 = f(), x2 = f(), x3 = f();
const void* p1 = x1.ptr;
const void* p2 = x2.ptr;
const void* p3 = x3.ptr;
std::cout << x1.ptr << " " << p1 << " " << p2 << " " << p3 << std::endl;
foo(x1);
foo(x2);
foo("test"_lw);
foo("test2"_lw);
}
根据您的具体需求,这可能适合您,也可能不适合您:
#include <cstdlib>
template <size_t N>
void foo(const char (&str)[N]) {}
template <char> struct check_literal {};
#define foo(arg) foo((check_literal<arg[0]>(),arg))
int main()
{
// This compiles
foo("abc");
// This does not
static const char abc[] = "abc";
foo(abc);
}
这仅适用于 -std=c++11
模式下的 g++ 和 clang++。