C - принудительное размещение строкового параметра в постоянной памяти

Я оптимизирую некоторый код, и у меня есть такая функция:

const char * gStrPtr = NULL;

void foo (const char *str) {
     gStrPtr = strdup(str);
}

На данный момент foo() вызывается только с постоянными строками. например:

const char fooStr[]="Some really long string...";
foo(fooStr);

Обратите внимание: поскольку он всегда вызывается с константой, я могу просто сделать:

void foo (const char *str) {
     gStrPtr=str;
}

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

Мне интересно, можно ли создать проверку во время компиляции или даже во время выполнения, которая проверяет, находится ли str в памяти только для чтения, чтобы избежать дорогостоящих поисков ошибок в будущем.

Примечание: если я предполагаю, что str является строковым литералом, я могу сделать это с помощью макроса следующим образом:

#define foo(str)  foo_func("" str)

что вызовет ошибки компиляции для не строковых литералов. Но он также не принимает указатели на константные символы.


РЕДАКТИРОВАТЬ

Я думал, что опубликую это после обсуждения ниже. @CraigEtsy указал на использование __builtin_constant_p, который является наилучшим подходом к этой проблеме (но, вероятно, будет достаточным для моих нужд). Я провел с этим следующие тесты и получил следующие результаты:

void foo(const char *str) {
        if (__builtin_constant_p(*str))
                printf("%s is constant\n", str);
        else
                printf("%s is not constant\n", str);
}

const char globalArray[] = "globalArray";
const char *globalPtr = "globalPtr";

int main()
{
    const char localArray[]="localArray";
    const char *localPtr="localPtr";
    char localNonConst[]="localNonConst";
    foo("literal");     // constant
    foo(localArray);    // not constant
    foo(localPtr);      // constant
    foo(globalArray);   // constant
    foo(globalPtr);     // not constant
    foo(localNonConst); // not constant
}

А при компиляции с -O3 дал результаты:

literal is constant
localArray is not constant
localPtr is constant
globalArray is constant
globalPtr is not constant
localNonConst is not constant

Итак, в моем конкретном случае я могу просто переключить const char arr[]="str" на const char * arr="str", а затем в моем foo() я могу проверить, является ли значение постоянным, и выделить память и поднять предупреждение во время выполнения, если нет (и отметить флаг, чтобы я знал освобождать ли указатель позже ...).

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

melpomene 13.09.2018 20:40

Нет, ты не можешь этого сделать.

Stargateur 13.09.2018 20:47

Я предположил, что захочу проверить, находится ли указатель в разделе .text ...

HardcoreHenry 13.09.2018 20:47

Что делать, если вы используете void myfunc() { const char mystr[] = "abc"; foo(mystr); }, а затем используете gStrPtr? mystr находится в постоянной памяти? Что такое постоянная память?

KamilCuk 13.09.2018 20:52

Память для переменной fooStr может быть помещена в память, не предназначенную только для чтения, но процедура инициализации вашего исполняемого файла может инициализировать память желаемым содержимым. Что тогда? Тогда fooStr не будет в памяти read-only, и вы можете изменить его (в этой архитектуре это зависит от реализации, вы не должны этого делать, и это поведение undefined).

KamilCuk 13.09.2018 20:57

@HardcoreHenry Не .rodata?

melpomene 13.09.2018 21:00

@melpomene - да, в .text или в .rodata (или в любом другом определяемом пользователем разделе, доступном только для чтения ...)

HardcoreHenry 13.09.2018 21:07

Любой метод проверки того, находится ли строка на странице, помеченной только для чтения (с использованием API ОС), будет дороже, чем strdup. Так поражает цель вашей «оптимизации».

Ajay Brahmakshatriya 13.09.2018 21:22

@KamilCuk - в вашем примере "abc", скорее всего, будет помещен в .text или .rodata, и, следовательно, будет неизменный (спасибо @R ...) ... однако я только что понял, что это также может быть в разделе .init, который может быть освобожден до использования gStrPtr .... вздох ...

HardcoreHenry 13.09.2018 21:22

Так и будет. Внутри формата файла ELF на архитектуре x86 или x86_64 с использованием компилятора gcc. На процессоре Cortex-M0 + с компилятором Keli с шестнадцатеричным выводом дело обстоит иначе. Нет переносимого способа сделать это, поэтому укажите, какие архитектуры и какие компиляторы вас интересуют.

KamilCuk 13.09.2018 21:28

Вы можете использовать [с gcc], функцию __builtin_constant_p и макрос, который выполняет strdup, если функция возвращает false. Он возвращает true, если вы даете ему "abc", и false в противном случае. Затем вы можете проверить диапазон по разделу .rodata и установить напрямую, если он находится внутри, но сделать strdup, если нет. Возможно, слишком много беспорядка, но я решил передать его.

Craig Estey 13.09.2018 21:30
__builtin_constant_p("abc") возвращает истину, но void *p = "abc"; __builtin_constant_p(p); возвращает ложь без оптимизации и истину с оптимизацией (gcc8.2.0). Я полагаю, что проверка раздела - единственный способ.
KamilCuk 13.09.2018 21:36

@KamilCuk - Я как раз разглядывал это. Я не понимаю, где проверяется постоянство p или постоянство того, на что он указывает ... Попробую несколько экспериментов.

HardcoreHenry 13.09.2018 21:44

См. gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html для информации о __builtin_constant_p. Он будет оценивать значение true, если известно, что выражение является константой времени компиляции. Знание этого требует в большинстве случаев оптимизации. Я считаю, что вам следует использовать *p, а не p, поскольку вы хотите знать, являются ли строковые константы постоянными, а не адрес строки.

R.. GitHub STOP HELPING ICE 13.09.2018 22:48

Стандарт C не имеет понятия постоянной памяти, только объектов. const не означает «постоянный», но является гарантией, данной программистом. Если вы хотите прострелить ногу, Си с радостью держит за вас пистолет.

too honest for this site 13.09.2018 23:40

Я внесу правку в свой пост, так как у меня недостаточно места в комментарии, но в основном в моей системе const char *ptr = "X" приведет к тому, что *ptr будет константой, но const char ptr[] = "X" НЕ приведет к тому, что *ptr будет считаться константой. Я думаю, что в моем конкретном случае я могу использовать этот метод (я всегда компилирую с оптимизацией и т. д.). @CraigEstey - если вы можете опубликовать это в качестве ответа, я его приму.

HardcoreHenry 14.09.2018 15:22
1
16
103
1

Ответы 1

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

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

Спасибо. Я пришел к такому же выводу, но подумал, что спрошу, если у кого-нибудь есть какие-то хитрые уловки, которые я пропустил. К сожалению, я не доверяю будущим программистам читать комментарии. Я могу заставить всех использовать литералы вместо массивов const char, а затем использовать трюк с макросами ...

HardcoreHenry 13.09.2018 21:01

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

R.. GitHub STOP HELPING ICE 13.09.2018 21:39

В зависимости от платформы проверка того, находится ли указатель в разделе данных только для чтения, намного дешевле, чем динамическое размещение.

M.M 14.09.2018 16:28

@ M.M: Возможно, но это большое «если», и в нем много предостережений, если только вы не находитесь в простом случае со статической связью. При динамической компоновке и загрузке вам понадобится функция для проверки наличия новых разделяемых библиотек каждый раз, когда она вызывается, а это будет намного дороже, чем выделение. Кроме того, считается ли постоянная память в общих библиотеках неизменной (что действительно нужно OP), если библиотека была загружена во время выполнения и может быть выгружена через dlclose? Я бы сказал нет, но дело в том, что определение даже неясно.

R.. GitHub STOP HELPING ICE 14.09.2018 16:32

Другие вопросы по теме