triángulo - Declarando cuerdas al estilo de Pascal en C
triangulo de pascal devc++ (10)
Aún puedes usar un const char *
literal y una secuencia de escape como su primer carácter que indica la longitud:
const char *pascal_string = "/x03foo";
Seguirá siendo terminada en cero, pero eso probablemente no importa.
En C, ¿hay una buena manera de definir primero la longitud, las cadenas de estilo Pascal como constantes, para que puedan colocarse en la ROM? (Estoy trabajando con un pequeño sistema integrado con un compilador ANSI C que no es GCC).
Una cadena C es 0
terminada, por ejemplo. { ''f''
, ''o''
, ''o''
, 0
}.
Una cadena de Pascal tiene la longitud en el primer byte, por ejemplo. { 3
, ''f''
, ''o''
, ''o''
}.
Puedo declarar que una cadena C se coloque en la ROM con:
const char *s = "foo";
Para una cadena de Pascal, pude especificar manualmente la longitud:
const char s[] = {3, ''f'', ''o'', ''o''};
Pero, esto es incómodo. ¿Hay alguna manera mejor? Tal vez en el preprocesador?
Aquí está mi respuesta, completa con una operación de adición que usa alloca () para el almacenamiento automático.
#include <stdio.h>
#include <string.h>
#include <alloca.h>
struct pstr {
unsigned length;
char *cstr;
};
#define PSTR(x) ((struct pstr){sizeof x - 1, x})
struct pstr pstr_append (struct pstr out,
const struct pstr a,
const struct pstr b)
{
memcpy(out.cstr, a.cstr, a.length);
memcpy(out.cstr + a.length, b.cstr, b.length + 1);
out.length = a.length + b.length;
return out;
}
#define PSTR_APPEND(a,b) /
pstr_append((struct pstr){0, alloca(a.length + b.length + 1)}, a, b)
int main()
{
struct pstr a = PSTR("Hello, Pascal!");
struct pstr b = PSTR("I didn''t C you there.");
struct pstr result = PSTR_APPEND(PSTR_APPEND(a, PSTR(" ")), b);
printf("/"%s/" is %d chars long./n", result.cstr, result.length);
return 0;
}
Podrías lograr lo mismo usando c strings y strlen. Debido a que tanto alloca como strlen prefieren cuerdas cortas, creo que eso tendría más sentido.
Creo que la siguiente es una buena solución, pero no olvide habilitar las estructuras empaquetadas:
#include <stdio.h>
#define DEFINE_PSTRING(var,str) const struct {unsigned char len; char content[sizeof(str)];} (var) = {sizeof(str)-1, (str)}
DEFINE_PSTRING(x, "foo");
/* Expands to following:
const struct {unsigned char len; char content[sizeof("foo")];} x = {sizeof("foo")-1, "foo"};
*/
int main(void)
{
printf("%d %s/n", x.len, x.content);
return 0;
}
Un problema es que agrega un byte NUL adicional después de su cadena, pero puede ser deseable porque también puede usarlo como una cadena c normal. También debe convertirlo en el tipo que esté esperando su biblioteca externa.
Esta es la razón por la cual las Matrices de Longitud Variable se introdujeron en c99 (y para evitar el uso de la "estructura hackeada") IIRC, las cadenas de Pascal se limitaron a una longitud máxima de 255.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h> // For CHAR_BIT
struct pstring {
unsigned char len;
char dat[];
};
struct pstring *pstring_new(char *src, size_t len)
{
struct pstring *this;
if (!len) len = strlen(src);
/* if the size does not fit in the ->len field: just truncate ... */
if (len >=(1u << (CHAR_BIT * sizeof this->len))) len = (1u << (CHAR_BIT * sizeof this->len))-1;
this = malloc(sizeof *this + len);
if (!this) return NULL;
this->len = len;
memcpy (this->dat, src, len);
return this;
}
int main(void)
{
struct pstring *pp;
pp = pstring_new("Hello, world!", 0);
printf("%p:[%u], %*.*s/n", (void*) pp
, (unsigned int) pp->len
, (unsigned int) pp->len
, (unsigned int) pp->len
, pp->dat
);
return 0;
}
GCC y clang (y posiblemente otros) aceptan la opción -fpascal-strings
que te permite declarar literales de cadena de estilo pascal haciendo que lo primero que aparezca en la cadena sea /p
, por ejemplo, "/pfoo"
. No exactamente portátil, pero ciertamente mejor que las macros funky o la construcción en tiempo de ejecución de ellas.
Vea here para más información.
Mi enfoque sería crear funciones para tratar con cadenas de Pascal:
void cstr2pstr(const char *cstr, char *pstr) {
int i;
for (i = 0; cstr[i]; i++) {
pstr[i+1] = cstr[i];
}
pstr[0] = i;
}
void pstr2cstr(const char *pstr, char *cstr) {
int i;
for (i = 0; i < pstr[0]; i++) {
cstr[i] = pstr[i+1];
}
cstr[i] = 0;
}
Entonces podría usarlo de esta manera:
int main(int arg, char *argv[]) {
char cstr[] = "ABCD", pstr[5], back[5];
cstr2pstr(cstr, pstr);
pstr2cstr(pstr, back);
printf("%s/n", back);
return 0;
}
Esto parece ser simple, directo, menos propenso a errores y no especialmente incómodo. Puede que no sea la solución a su problema, pero le recomendaría que al menos piense en usarlo.
Puede definir una matriz de la forma que desee, pero tenga en cuenta que esta sintaxis no es adecuada:
const char *s = {3, ''f'', ''o'', ''o''};
Necesitas una matriz en lugar de un puntero:
const char s[] = {3, ''f'', ''o'', ''o''};
Tenga en cuenta que un char
solo almacenará números hasta 255 (considerando que no está firmado) y esta será la longitud máxima de su cadena.
Sin embargo, no espere que esto funcione donde lo harían otras cuerdas. Se espera que la cadena de CA termine con un carácter nulo no solo por el compilador, sino por todo lo demás.
Puede sonar un poco extremo, pero si tiene muchas cadenas de este tipo que necesitan actualizaciones frecuentes, puede considerar escribir su propia herramienta pequeña (¿quizás un script en perl?) Que se ejecuta en el sistema host, analiza un archivo de entrada con un formato personalizado Puedes diseñar a tu gusto y generar un archivo .c. Puedes integrarlo a tu makefile o lo que sea y vivir feliz para siempre :)
Estoy hablando de un programa que convertirá esta entrada (u otra sintaxis que prefiera):
s = "foo";
x = "My string";
Para esta salida, que es un archivo .c:
const char s[] = {3, ''f'', ''o'', ''o''};
const char x[] = {9, ''M'', ''y'', '' '', ''s'', ''t'', ''r'', ''i'', ''n'', ''g''};
Puedes aplicar sizeof
a cadenas literales también. Esto permite un poco menos incómodo.
const char s[] = {sizeof "foo" - 1u, ''f'', ''o'', ''o''};
Tenga en cuenta que el tamaño de una cadena literal incluye el carácter NUL de terminación, por lo que debe restar 1. Pero aún así, es mucho escribir y ofuscar :-)
Una opción podría ser abusar del preprocesador. Al declarar una estructura del tamaño correcto y poblarla en la inicialización, puede ser const
.
#define DECLARE_PSTR(id,X) /
struct pstr_##id { char len; char data[sizeof(X)]; }; /
static const struct pstr_##id id = {sizeof(X)-1, X};
#define GET_PSTR(id) (const char *)&(id)
#pragma pack(push)
#pragma pack(1)
DECLARE_PSTR(bob, "foo");
#pragma pack(pop)
int main(int argc, char *argv[])
{
const char *s = GET_PSTR(bob);
int len;
len = *s++;
printf("len=%d/n", len);
while(len--)
putchar(*s++);
return 0;
}