Объявление строк в стиле Паскаля в C
В C, есть ли хороший способ определить длину сначала, строки в стиле Pascal как константы, чтобы их можно было помещать в ПЗУ? (Я работаю с небольшой встроенной системой с компилятором ANSI C, отличным от GCC).
С-строка 0
завершена, например. { 'f'
, 'o'
, 'o'
, 0
}.
Строка Pascal имеет длину в первом байте, например. { 3
, 'f'
, 'o'
, 'o'
}.
Я могу объявить C-строку, которая будет помещена в ПЗУ, с помощью:
const char *s = "foo";
Для строки Pascal я могу вручную указать длину:
const char s[] = {3, 'f', 'o', 'o'};
Но это неудобно. Есть ли способ лучше? Возможно, в препроцессоре?
Ответы
Ответ 1
Я думаю, что следующее является хорошим решением, но не забудьте включить упакованные структуры:
#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;
}
Один улов, он добавляет дополнительный NUL-байт после вашей строки, но это может быть желательно, потому что тогда вы можете использовать его как обычную строку c. Вам также необходимо передать его в любой тип, который ожидает ваша внешняя библиотека.
Ответ 2
GCC и clang (и, возможно, другие) принимают параметр -fpascal-strings
, который позволяет объявлять строковые литералы в стиле pascal, имея первое, что появляется в строке, как \p
, например. "\pfoo"
. Не совсем портативные, но, безусловно, приятнее, чем фанки-макросы или их исполнение.
Подробнее см. здесь.
Ответ 3
Вы все равно можете использовать литерал const char *
и escape-последовательность в качестве своего первого символа, который указывает длину:
const char *pascal_string = "\x03foo";
Он по-прежнему будет иметь нулевое завершение, но это, вероятно, не имеет значения.
Ответ 4
Мой подход заключался бы в создании функций для работы с строками Паскаля:
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;
}
Тогда я мог бы использовать его следующим образом:
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;
}
Это кажется простым, простым, менее подверженным ошибкам и не особо неудобным. Это может быть не решение вашей проблемы, но я бы рекомендовал вам по крайней мере подумать об использовании этого.
Ответ 5
Вы можете применить sizeof
к строковым литералам. Это позволяет немного менее неудобно
const char s[] = {sizeof "foo" - 1u, 'f', 'o', 'o'};
Обратите внимание, что sizeof строкового литерала включает в себя завершающий символ NUL, поэтому вы должны вычесть 1. Но все же, это много набивки и обфускации: -)
Ответ 6
Это может показаться немного экстремальным, но если у вас много таких строк, которые требуют частого обновления, вы можете подумать о том, чтобы написать собственный небольшой инструмент (perl script, возможно?), который выполняется в главной системе, анализирует входной файл с пользовательским форматом, который вы можете создать по своему вкусу и выводит .c файл. Вы можете интегрировать его в свой makefile или что-то еще и жить долго и счастливо:)
Я говорю о программе, которая будет конвертировать этот вход (или другой синтаксис, который вы предпочитаете):
s = "foo";
x = "My string";
Для этого вывода, который является .c файлом:
const char s[] = {3, 'f', 'o', 'o'};
const char x[] = {9, 'M', 'y', ' ', 's', 't', 'r', 'i', 'n', 'g'};
Ответ 7
Одним из вариантов может быть злоупотребление препроцессором. Объявив структуру нужного размера и запустив ее при инициализации, она может быть 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;
}
Ответ 8
Вот почему переменные длины массивов были введены в c99 (и, чтобы избежать использования "хачка структуры" ) IIRC, строки Pascal были ограничены максимальной длиной 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;
}
Ответ 9
Вы можете определить массив так, как вам нравится, но обратите внимание, что этот синтаксис недостаточен:
const char *s = {3, 'f', 'o', 'o'};
Вам нужен массив вместо указателя:
const char s[] = {3, 'f', 'o', 'o'};
Обратите внимание, что a char
будет хранить номера до 255 (учитывая, что они не подписаны), и это будет ваша максимальная длина строки.
Не ожидайте, что это сработает там, где другие строки будут. Ожидается, что строка C завершится с нулевым символом не только компилятором, но и всем остальным.
Ответ 10
Здесь мой ответ, в комплекте с операцией append, которая использует alloca() для автоматического хранения.
#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;
}
Вы можете выполнить одно и то же, используя строки c и strlen. Поскольку alloca и strlen предпочитают короткие строки, я думаю, что это будет иметь больше смысла.