c++ - initialize - Diferenciar cadena Literal de Char Array
string character c (3)
Quiero escribir alguna función que tome una cadena literal, y solo una cadena literal:
template <size_t N>
void foo(const char (&str)[N]);
Desafortunadamente, eso es demasiado expansivo y coincidirá con cualquier matriz de caracteres, ya sea que se trate de un verdadero literal de cadena o no. Si bien es imposible distinguir la diferencia entre estos en tiempo de compilación, sin tener que recurrir a que la persona que llama envuelva el literal / matriz, en el tiempo de ejecución, las dos matrices estarán en lugares completamente diferentes en la memoria:
foo("Hello"); // at 0x400f81
const char msg[] = {''1'', ''2'', ''3''};
foo(msg); // at 0x7fff3552767f
¿Hay alguna manera de saber en qué lugar de la memoria podrían vivir los datos de cadena para que al menos pueda assert
que la función solo toma una cadena literal? (Usando gcc 4.7.3, pero realmente una solución para cualquier compilador sería genial).
Parece asumir que un rasgo necesario de un "verdadero literal de cadena" es que el compilador lo hornea en el almacenamiento estático del ejecutable.
Esto no es realmente cierto. Los estándares C y C ++ nos garantizan que un literal de cadena tendrá una duración de almacenamiento estático, por lo que debe existir durante la vigencia del programa, pero si un compilador puede organizarlo sin colocar el literal en el almacenamiento estático, es libre de hacerlo, y algunos compiladores a veces lo hacen.
Sin embargo, está claro que la propiedad que desea probar, para un literal de cadena dado, es si de hecho está en almacenamiento estático . Y como no es necesario que esté en almacenamiento estático, en lo que respecta a los estándares de idioma, no puede haber ninguna solución de su problema fundada únicamente en C / C ++ portátil.
Si un literal de cadena dado está realmente en almacenamiento estático es la cuestión de si la dirección del literal de cadena se encuentra dentro de uno de los rangos de direcciones que se asignan a las secciones de vinculación que califican como almacenamiento estático , en la nomenclatura de su cadena de herramientas particular, cuando su programa está construido por esa cadena de herramientas.
Así que la solución que sugiero es que habilite su programa para conocer los rangos de direcciones de las secciones de sus propios enlaces que califican como almacenamiento estático , y luego puede probar si un literal de cadena dado está en almacenamiento estático por código obvio.
Aquí hay una ilustración de esta solución para un proyecto de juguetes C ++, prog
construido con la cadena de herramientas GNU / Linux x86_64 (C ++ 98 o mejor lo hará, y el enfoque es solo un poco más complicado para C). En esta configuración, vinculamos en formato ELF, y las secciones de enlace consideraremos que el almacenamiento estático es .bss
(0 datos estáticos inicializados), .rodata
(estático estático de solo lectura) y .data
(datos estáticos de lectura / escritura).
Aquí están nuestros archivos fuente:
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;
}
Aquí está nuestro archivo MAKE:
.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
Esto es lo que make
lucir así:
$ 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
Y, por último, lo que hace el 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
Si es obvio cómo funciona esto, no necesita leer más.
El programa compilará y vinculará incluso antes de que sepamos las direcciones y tamaños de sus secciones de almacenamiento estáticas. También lo necesitaría, ¿no? En ese caso, las variables section_*
globales que deberían contener estos valores se construyen con valores de section_*
posición.
Cuando se ejecuta make
, las recetas:
$(TARG): $(MAP_FILE)
$(MAKE) AGAIN=1
y
$(MAP_FILE): $(OBJS)
g++ -o $(TARG) $(CXXFLAGS) -Wl,-Map=$@ $(OBJS) $(LDLIBS)
touch section_bounds.cpp
son operativos, porque AGAIN
no está definido. Dicen que para construir prog
, primero debe compilar el archivo del mapa del enlazador de prog
, según la segunda receta, y luego volver a section_bounds.cpp
hora section_bounds.cpp
. Después de eso, make
es llamar a sí mismo nuevamente, con AGAIN
definido = 1.
Excediendo el archivo MAKE otra vez, con AGAIN
definido, make
ahora encuentra que debe calcular todas las variables:
BSS_BOUNDS
BSS_START
BSS_SIZE
RODATA_BOUNDS
RODATA_START
RODATA_SIZE
DATA_BOUNDS
DATA_START
DATA_SIZE
Para cada sección de almacenamiento estático S
, calcula S_BOUNDS
volcando el archivo de correlación del enlazador para la línea que informa la dirección y el tamaño de S
Desde esa línea, asigna la 2da palabra (= la dirección de la sección) a S_START
, y la 3ra palabra (= el tamaño de la sección) a S_SIZE
. Todos los valores de delimitación de sección se anexan a través de las opciones -D
a los CPPFLAGS
que se pasarán automáticamente a las compilaciones.
Como se define AGAIN
, la receta operativa para $(TARG)
es ahora la habitual:
$(TARG): $(OBJS)
g++ -o $@ $(CXXFLAGS) $(OBJS) $(LDLIBS)
Pero tocamos section_bounds.cpp
en la make
padre; por lo tanto, tiene que recompilarse y, por lo tanto, se debe volver a vincular el progreso. Esta vez, cuando se compila section_bounds.cpp
, todas las macros delimitadoras de sección:
BSS_START
BSS_SIZE
RODATA_START
RODATA_SIZE
DATA_START
DATA_SIZE
tendrá valores predefinidos y no asumirá sus valores de marcador de posición.
Y esos valores predefinidos serán correctos porque el segundo enlace no agrega símbolos al enlace y no elimina ninguno, y no altera el tamaño o la clase de almacenamiento de ningún símbolo. Simplemente asigna diferentes valores a los símbolos que estaban presentes en el primer enlace. En consecuencia, las direcciones y tamaños de las secciones de almacenamiento estáticas no se verán alteradas y su programa ya las conoce.
Puede usar literales definidos por el usuario, que por definiciones solo se pueden aplicar a literales:
#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);
}
Según lo que desee, esto puede funcionar o no para usted:
#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);
}
Esto funciona solo con g ++ y clang ++ en modo -std=c++11
.