Ответ 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
будет иметь заранее определенные значения и не будет принимать их значения владельца места.
И эти предопределенные значения будут правильными, потому что вторая привязка не добавляет никаких символов в ссылку и не удаляет ни одного, и не изменяет размер или класс хранения любого символа. Он просто присваивает разные значения символы, которые присутствовали в первой связи. Следовательно, адреса и размеры разделов статического хранилища будут неизменными и теперь известны вашей программе.