Отключение NUL-завершения строк в GCC

Можно ли глобально отключить строки с нулевым завершением в GCC?

Я использую свою собственную библиотеку строк, и мне абсолютно не нужны конечные символы NUL, поскольку она уже хранит правильную длину внутри структуры.

Однако, если бы я хотел добавить 10 строк, это означало бы, что в стеке излишне выделено 10 байтов. С широкими строками дело обстоит еще хуже: На x86 теряется 40 байт; а для x86_64 80 байт!

Я определил макрос, чтобы добавить эти строки, выделенные в стеке, в мою структуру:

#define AppendString(ppDest, pSource) \
  AppendSubString(ppDest, (*ppDest)->len + 1, pSource, 0, sizeof(pSource) - 1)

Использование sizeof(...) - 1 работает довольно хорошо, но мне интересно, могу ли я избавиться от завершения NUL, чтобы сэкономить несколько байтов?


person user206268    schedule 20.11.2009    source источник
comment
Что должно происходить с пустыми строками? Как вы предлагаете с ними бороться?   -  person Sinan Ünür    schedule 20.11.2009
comment
Я почти уверен, что 80 байт не будут проблемой для вашей программы. Особенно на х86.   -  person Carl Norum    schedule 20.11.2009
comment
В Linux и я предполагаю, что все остальные ОС wchar_t имеют одинаковую длину независимо от архитектуры. Linux, в частности, использует 4 байта как для x86, так и для x86_64.   -  person intgr    schedule 20.11.2009
comment
Удачи вам в замене строковых функций в C вашими собственными версиями. И все функции, которым вы передаете строку с завершающим нулем. И получить адекватную производительность. Не могли бы вы объяснить немного больше о своей проблеме, чтобы вас не кричали за попытку сохранить байт, выделив два в начале строки, пожалуйста?   -  person Matthew Farwell    schedule 20.11.2009
comment
Действительно, это явно преждевременная оптимизация. Даже если многие языковые интерпретаторы определяют свои собственные строковые типы, во многих случаях они по-прежнему включают 0 байт, поскольку он незначителен по сравнению с накладными расходами поля четной длины/malloc.   -  person intgr    schedule 20.11.2009
comment
Во-первых, проблема, которую вы описываете, возможна только тогда, когда все строки являются строковыми литералами, то есть все они известны во время компиляции. Довольно странно иметь дело с проблемой, ввод которой известен во время компиляции, особенно когда речь идет о таких оптимизациях.   -  person AnT    schedule 20.11.2009
comment
Во-вторых, если вы действительно хотите избавиться от нулевых терминаторов, просто инициализируйте свои массивы символов побуквенно, как в char s[5] = { 'H', 'e', 'l', 'l', 'o' }.   -  person AnT    schedule 20.11.2009
comment
Почему, во имя Чарльза Бэббиджа, вы так заботитесь об экономии места? Если вы не работаете над какой-то чрезвычайно странной проблемой, о которой не упомянули, вам не нужно этого делать, и вы создадите себе гораздо больше проблем, чем решите.   -  person David Thornley    schedule 20.11.2009
comment
@Sinan Ünür - Если у вас есть поле размера, вы можете установить нулевой размер для пустых строк.   -  person Swiss    schedule 20.11.2009
comment
Помните, что когда вы передаете строку функции, вы на самом деле передаете указатель и sizeof (ptr) ничего не знает об исходной строке.   -  person pmg    schedule 20.11.2009
comment
Я ожидал таких комментариев, когда задавал свой вопрос. :) @MatthieuF: Как видите, AppendSubString() ожидает длину в качестве своего последнего параметра. Это позволяет мне продолжать использовать функции Glibc, пока я не заменю их своими собственными. Вот пример из моей HTTP-библиотеки: bytesRcvd = recv(sock, buf, RCVBUFSIZE, 0); /* ... */ AppendSubString(response, (*response)-›len + 1, buf, 0, bytesRcvd);   -  person user206268    schedule 20.11.2009
comment
Я реализовал только 10 функций, и они охватывают все операции со строками, которые мне нужны до сих пор. Хотя они очень общие. Например, есть функция SubStringEquals() и три макроса (StringBegins, StringEnds, StringEquals), использующие ее.   -  person user206268    schedule 20.11.2009


Ответы (7)


Это довольно ужасно, но вы можете явно указать длину каждой константы массива символов:

char my_constant[6] = "foobar";
assert(sizeof my_constant == 6);

wchar_t wide_constant[6] = L"foobar";
assert(sizeof wide_constant == 6*sizeof(wchar_t));
person intgr    schedule 20.11.2009
comment
Вы можете сделать это макросом: #define NEW_STRING(var, val) char var[sizeof(val)-1] = val - person mbauman; 20.11.2009
comment
Если ваш собственный строковый тип определен как структура {length; pointer}, то вы также можете использовать length=0 и pointer=NULL — доступ к указателю в любом случае будет недействительным, потому что нет символов для чтения. - person intgr; 20.11.2009
comment
@intgr: С вашим кодом я получаю: ошибка: массив широких символов инициализирован из неширокой строки @Sinan Ünür: Даже пустые строки заканчиваются NUL. Это означает, что sizeof() - 1 возвращает 0, как и ожидалось. В моем случае AppendSubString() сразу остановится и ничего не добавит. @ Мэтт Б.: Я знаю об этом, но на самом деле это не решает мою проблему. Макрос AppendString() используется в моем коде везде следующим образом: AppendString(&string, Hello World); Как видите, я не использую никаких переменных для этой цели. - person user206268; 20.11.2009
comment
@timn: Ой, я забыл, что вам нужно ставить префикс L"" перед широкосимвольными литералами. Я обновил свой пример. - person intgr; 20.11.2009
comment
@timn: в случае строкового литерала (как вы цитируете AppendString(&string, "Hello World")) "Hello World" не хранится в стеке (по крайней мере, для x86 *). Он хранится в доступном только для чтения сегменте исполняемого файла, и AppendString получает указатель на этот сегмент. Таким образом, в этом случае вы увеличиваете не размер стека, а размер исполняемого файла. Я, конечно, надеюсь, что один байт не сломает вас в любом случае, но это даже не то, о чем вы беспокоитесь. - person mbauman; 20.11.2009
comment
Мэтт Б.: А, это интересно. Теперь я могу продолжать использовать текущий подход, не испытывая никаких негативных чувств по поводу использования памяти. :) - person user206268; 21.11.2009
comment
Нет, ты не можешь. По крайней мере, он не портативный. gcc-4.7.0 сообщает ошибка: слишком длинная строка инициализатора для массива символов [-fpermissive]. - person Maxim Egorushkin; 26.04.2012
comment
@MaximYegorushkin Итак, вы проголосовали против, потому что я не мог предсказать будущее 2,5 года назад, что будущая версия GCC сочтет это ошибкой? - person intgr; 26.04.2012
comment
Пример по-прежнему работает с компилятором GCC C. Да, компилятор C++ жалуется. - person intgr; 26.04.2012
comment
Справедливо, я должен был проверить тег. - person Maxim Egorushkin; 26.04.2012

Я понимаю, что вы имеете дело только со строками, объявленными в вашей программе:

 ....
 char str1[10];
 char str2[12];
 ....

а не с текстовыми буферами, которые вы выделяете malloc() и друзьям, иначе sizeof вам не поможет.

В любом случае, я бы дважды подумал об удалении \0 в конце: вы потеряете совместимость с функциями стандартной библиотеки C.

Если вы не собираетесь переписывать какую-либо одну строковую функцию для своей библиотеки (например, sprintf), вы уверены, что хотите это сделать?

person Remo.D    schedule 20.11.2009
comment
Я не использую строки, размещенные в куче, потому что это именно то, для чего предназначена моя библиотека строк. Выделение памяти вручную слишком опасно, так как всегда есть риск переполнения буфера. Как было сказано выше, переписать строковые функции было несложно. Благодаря C99 я могу использовать простой хак, чтобы сохранить совместимость с функциями Glibc: char tmp[string-›len + 1]; tmp[string-›len + 1] = '\0'; printf(%s, tmp); - person user206268; 20.11.2009
comment
Я уверен, что у вас есть веские причины для этого, но, если я понимаю, как работают эксплойты переполнения буфера, наличие строки в стеке более опасно, чем размещение их в куче. Что касается добавления «\ 0» в конце, я не вижу, как содержимое строки копируется во временную папку, и я думаю, что вы используете расширение gcc, а не C99. - person Remo.D; 21.11.2009

Я не могу вспомнить подробности, но когда я помню

char my_constant[5]

возможно, что он все равно зарезервирует 8 байтов, потому что некоторые машины не могут адресовать середину слова.

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

person Matthew Farwell    schedule 20.11.2009
comment
Это называется выравниванием (также внутренней фрагментацией). Это правда, что отбрасывание байта NUL не уменьшает большинство строк, но когда это происходит, оно уменьшается на размер выравнивания. Таким образом, в среднем каждая строка будет потреблять на 1 байт меньше памяти. - person intgr; 20.11.2009

Если вы не используете какие-либо функции стандартной библиотеки, работающие со строками, вы можете забыть о завершающем байте NUL.

Нет strlen(), нет fgets(), нет atoi(), нет strtoul(), нет fopen(), нет printf() со спецификатором преобразования %s...

Объявите свои «не совсем строки C» только с необходимым пространством;

struct NotQuiteCString { /* ... */ };

struct NotQuiteCString variable;
variable.data = malloc(5);
data[0] = 'H'; /* ... */ data[4] = 'o'; /* "hello" */
person pmg    schedule 20.11.2009
comment
В основном это более или менее то, чем я сейчас занимаюсь. Возможно, было бы действительно лучше оставить NUL-завершение вообще включенным, но, возможно, есть что-то вроде прагмы, позволяющей мне включать/отключать завершающий байт NUL для данной части кода, то есть функции моей библиотеки строк? - person user206268; 20.11.2009
comment
Просто не используйте его. Вы используете массивы int каждый день, много раз в день... и вы ни разу не использовали завершающий int. Сделайте то же самое с массивами char (когда я делаю это, я специально подписываю свои chars: signed char или unsigned char: для меня только простые char могут быть C строками). - person pmg; 20.11.2009

На самом деле это только в том случае, если у вас действительно мало памяти. В противном случае я не рекомендую этого делать.

Кажется, самый правильный способ сделать то, о чем вы говорите, это:

  • Чтобы подготовить минимальный файл «листинга» в виде:
    string1_constant_name "str1"
    string2_constant_name "str2"
    ...
  • Чтобы создать утилиту, которая обрабатывает ваш файл и генерирует объявления, такие как
    const char string1_constant[4] = "str1";

Конечно, делать это руками я бы не советовал, иначе после любой смены струны могут возникнуть проблемы.

Итак, теперь у вас есть обе строки без завершения из-за фиксированных автоматически сгенерированных массивов, а также у вас есть sizeof() для каждой переменной. Это решение кажется приемлемым.

Преимущества: простая локализация, возможность добавления определенного уровня проверок, чтобы снизить риск этого решения, и экономия сегментов данных R/O.

Недостаток заключается в том, что необходимо включать все такие строковые константы в каждый модуль (как включить, чтобы сохранить известность sizeof()). Так что это имеет смысл только в том случае, если ваш компоновщик объединяет такие символы (некоторые этого не делают).

person Roman Nikitchenko    schedule 20.11.2009
comment
Хорошая идея! К сожалению, перед компиляцией мне нужно всегда пропускать весь код через препроцессор. Если в GCC действительно нет такой возможности отключить завершение NUL, я буду придерживаться своего текущего подхода. - person user206268; 20.11.2009

Разве они не похожи на строки в стиле Паскаля или строки Холлерита? Я думаю, что это полезно только в том случае, если вы действительно хотите, чтобы данные String сохраняли NULL, в которых вы действительно используете произвольную память, а не «строки» как таковые.

person Fletch    schedule 25.11.2009
comment
Да, я использую их так же, как и струны Холлерита. В моем коде он определяется следующим образом: typedef struct { unsigned int len; беззнаковое целое maxLen; символ buf[0]; } Есть и другие преимущества. Википедия (взято из String literal) говорит: * устраняет текстовый поиск (для символа-разделителя) и, следовательно, требует значительно меньших накладных расходов * избегает (100% вызванной программистом) проблемы столкновения разделителей * позволяет включать метасимволы, которые в противном случае могли бы быть ошибочно приняты за команды * можно использовать для достаточно эффективного сжатия данных простых текстовых строк - person user206268; 29.11.2009

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

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

Наличие оболочки, которая использует strlen для определения длины строки и не является встроенной, почти наверняка сэкономит больше места.

person Remember Monica    schedule 26.04.2012