区分字符串文本和字符数组

Differentiate String Literal from Char Array

本文关键字:字符 数组 文本 字符串      更新时间:2023-10-16

我想写一些函数来接受字符串文字 - 而且只有一个字符串文字:

template <size_t N>
void foo(const char (&str)[N]);

不幸的是,这太宽泛了,并且会匹配任何char数组 - 无论它是否是真正的字符串文本。虽然在编译时无法区分它们 - 不必诉诸要求调用者包装文字/数组 - 但在运行时,这两个数组将位于内存中完全不同的位置:

foo("Hello"); // at 0x400f81
const char msg[] = {'1', '2', '3'};
foo(msg); // at 0x7fff3552767f

有没有办法知道字符串数据可以存放在内存中的位置,以便我至少可以assert该函数仅接受字符串文字?(使用 gcc 4.7.3,但实际上任何编译器的解决方案都会很棒(。

您似乎假设"真正的字符串文字"的必要特征是编译器将其烘焙到可执行文件的静态存储中。

这实际上不是真的。C 和 C++ 标准向我们保证字符串文本应具有静态存储持续时间,因此它必须存在程序的寿命,但如果编译器可以在不放置的情况下安排它静态存储中的文字,可以免费这样做,有时有些编译器做。

但是,很明显,对于给定字符串,您要测试的属性字面意思,是它是否实际上在静态存储中。而且由于它不需要在静态存储中,只要语言标准保证,就有不能仅基于便携式C/C++解决您的问题。

给定的字符串文本实际上是否在静态存储中是一个问题字符串文本的地址是否位于分配给符合条件的链接部分的地址范围静态存储,在特定工具链的命名法中,当您的程序是由该工具链构建的。

所以我建议的解决方案是让你的程序知道符合静态存储,然后它可以测试给定的字符串是否文本通过明显的代码在静态存储中。

以下是该解决方案在玩具C++项目中的图示,prog 使用 GNU/Linux x86_64工具链(C++98 或更高版本(构建即可,并且对于 C 来说,方法只是稍微繁琐一些(。在此设置中,我们在 ELF 中链接格式,以及我们认为静态存储的链接部分是.bss(0-初始化的静态数据(,.rodata (只读静态静态(和.data(读/写静态数据(。

以下是我们的源文件:

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 datan";
    } else if (addr >= section_rodata_start && addr < section_rodata_end) {
        cout << "then s is in static read-only datan";     
    } else if (addr >= section_data_start && addr < section_data_end){
        cout << "then s is in static read/write datan";
    } else {
        cout << "then s is on the stack/heapn";
    }       
}

主.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;
}

这是我们的制作文件:

.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。

再次执行生成文件,并定义AGAINmake现在发现它必须计算所有变量:

BSS_BOUNDS
BSS_START
BSS_SIZE
RODATA_BOUNDS
RODATA_START
RODATA_SIZE
DATA_BOUNDS
DATA_START
DATA_SIZE

对于每个静态存储部分S,它通过 grepping 来计算S_BOUNDS报告S 的地址和大小的行的链接器映射文件。从该行中,它将第二个单词( = 部分地址(分配给 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

将具有预定义的值,并且不会假定其占位符值。

这些预定义的值将是正确的,因为第二个链接不向链接添加任何符号,也不删除任何符号,并且不会更改任何符号的大小或存储类。它只是将不同的值分配给第一个链接中存在的符号。因此,静态存储部分的地址和大小将保持不变,现在程序知道。

根据你到底想要什么,这可能对你有用,也可能对你不起作用:

#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++。

您可以使用用户定义的文本,根据定义,这些文本只能应用于文本:

#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);
}