Подтвердить что ты не робот

Дифференцировать строковый литерал из массива Char

Я хочу написать некоторую функцию, которая принимает строковый литерал - и только строковый литерал:

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, но на самом деле решение для любого компилятора было бы замечательным).

4b9b3361

Ответ 1

Вы, кажется, предполагаете, что необходимая черта "истинного строкового литерала" заключается в том, что компилятор превращает его в статическое хранилище исполняемого файла.

На самом деле это не так. Стандарты C и С++ гарантируют нам, что строковый литерал должен иметь статическую продолжительность хранения, поэтому он должен существовать для жизни программы, но если компилятор может организовать это без размещения литерал в статическом хранилище, он свободен, и иногда некоторые компиляторы сделать.

Однако ясно, что свойство, которое вы хотите проверить, для данной строки буквально, является ли это фактически в статическом хранилище. И поскольку это не обязательно быть в статическом хранилище, насколько гарантируют языковые стандарты, там не может быть решением вашей проблемы, основанной исключительно на переносном C/С++.

Является ли данный строковый литерал на самом деле в статическом хранилище, является вопрос о том, находится ли адрес строкового литерала в одном из диапазоны адресов, которые присваиваются разделам связей, которые квалифицируются как статическое хранилище, в номенклатуре вашей конкретной инструментальной цепочки, когда ваша программа построена с помощью этой инструментальной цепочки.

Итак, решение, которое я предлагаю, - это то, что вы позволяете вашей программе знать диапазоны адресов тех из его собственных разделов связи, которые квалифицируются как статического хранения, а затем он может проверить, является ли данный строковый литерал находится в статическом хранилище с помощью очевидного кода.

Вот иллюстрация этого решения для игрушечного проекта С++, prog построенный с помощью инструментальной цепочки GNU/Linux x86_64 (С++ 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 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,[email protected] $(OBJS) $(LDLIBS)
    touch section_bounds.cpp

$(TARG): $(MAP_FILE)
    $(MAKE) AGAIN=1
else
$(TARG): $(OBJS)
    g++ -o [email protected] $(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,[email protected] $(OBJS) $(LDLIBS)
    touch section_bounds.cpp

действуют, поскольку AGAIN undefined. Они говорят make, что для того, чтобы для построения prog он должен сначала создать файл карты компоновщика prog, согласно второй рецепт, а затем повторите отметку времени section_bounds.cpp. После этого, make должен вызвать себя снова, с AGAIN defined = 1.

Повторное выполнение make файла, с AGAIN, make теперь обнаруживает, что это должен вычислять все переменные:

BSS_BOUNDS
BSS_START
BSS_SIZE
RODATA_BOUNDS
RODATA_START
RODATA_SIZE
DATA_BOUNDS
DATA_START
DATA_SIZE

Для каждого раздела статического хранилища S он вычисляет S_BOUNDS с помощью grepping файл карты компоновщика для строки, которая сообщает адрес и размер S. Из этой строки он назначает второе слово (= адрес раздела) на S_START, и третье слово (= размер раздела) до S_SIZE. Все секции затем добавляются значения с разделителями через -D параметры CPPFLAGS который будет автоматически передан в компиляции.

Поскольку AGAIN определен, оперативный рецепт для $(TARG) теперь является обычным:

$(TARG): $(OBJS)
    g++ -o [email protected] $(CXXFLAGS) $(OBJS) $(LDLIBS)

Но мы коснулись section_bounds.cpp в родительском make; так что это должно быть перекомпилирован, и поэтому prog нужно переделать. На этот раз, когда section_bounds.cpp скомпилирован, все макросы с разделителями разделов:

BSS_START
BSS_SIZE
RODATA_START
RODATA_SIZE
DATA_START
DATA_SIZE

будет иметь заранее определенные значения и не будет принимать их значения владельца места.

И эти предопределенные значения будут правильными, потому что вторая привязка не добавляет никаких символов в ссылку и не удаляет ни одного, и не изменяет размер или класс хранения любого символа. Он просто присваивает разные значения символы, которые присутствовали в первой связи. Следовательно, адреса и размеры разделов статического хранилища будут неизменными и теперь известны вашей программе.

Ответ 2

В зависимости от того, что именно вы хотите, это может работать или не работать для вас:

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

Это работает только с g++ и clang++ только в -std=c++11.

Ответ 3

Вы можете использовать пользовательские литералы, которые по определению могут применяться только к литералам:

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