Я оптимизирую некоторый код, и у меня есть такая функция:
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()
я могу проверить, является ли значение постоянным, и выделить память и поднять предупреждение во время выполнения, если нет (и отметить флаг, чтобы я знал освобождать ли указатель позже ...).
Нет, ты не можешь этого сделать.
Я предположил, что захочу проверить, находится ли указатель в разделе .text
...
Что делать, если вы используете void myfunc() { const char mystr[] = "abc"; foo(mystr); }
, а затем используете gStrPtr? mystr
находится в постоянной памяти? Что такое постоянная память?
Память для переменной fooStr
может быть помещена в память, не предназначенную только для чтения, но процедура инициализации вашего исполняемого файла может инициализировать память желаемым содержимым. Что тогда? Тогда fooStr
не будет в памяти read-only
, и вы можете изменить его (в этой архитектуре это зависит от реализации, вы не должны этого делать, и это поведение undefined).
@HardcoreHenry Не .rodata
?
@melpomene - да, в .text
или в .rodata
(или в любом другом определяемом пользователем разделе, доступном только для чтения ...)
Любой метод проверки того, находится ли строка на странице, помеченной только для чтения (с использованием API ОС), будет дороже, чем strdup
. Так поражает цель вашей «оптимизации».
@KamilCuk - в вашем примере "abc", скорее всего, будет помещен в .text
или .rodata
, и, следовательно, будет неизменный (спасибо @R ...) ... однако я только что понял, что это также может быть в разделе .init
, который может быть освобожден до использования gStrPtr .... вздох ...
Так и будет. Внутри формата файла ELF на архитектуре x86 или x86_64 с использованием компилятора gcc. На процессоре Cortex-M0 + с компилятором Keli с шестнадцатеричным выводом дело обстоит иначе. Нет переносимого способа сделать это, поэтому укажите, какие архитектуры и какие компиляторы вас интересуют.
Вы можете использовать [с gcc
], функцию __builtin_constant_p
и макрос, который выполняет strdup
, если функция возвращает false. Он возвращает true, если вы даете ему "abc"
, и false в противном случае. Затем вы можете проверить диапазон по разделу .rodata
и установить напрямую, если он находится внутри, но сделать strdup
, если нет. Возможно, слишком много беспорядка, но я решил передать его.
__builtin_constant_p("abc")
возвращает истину, но void *p = "abc"; __builtin_constant_p(p);
возвращает ложь без оптимизации и истину с оптимизацией (gcc8.2.0). Я полагаю, что проверка раздела - единственный способ.
@KamilCuk - Я как раз разглядывал это. Я не понимаю, где проверяется постоянство p или постоянство того, на что он указывает ... Попробую несколько экспериментов.
См. gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html для информации о __builtin_constant_p
. Он будет оценивать значение true, если известно, что выражение является константой времени компиляции. Знание этого требует в большинстве случаев оптимизации. Я считаю, что вам следует использовать *p
, а не p
, поскольку вы хотите знать, являются ли строковые константы постоянными, а не адрес строки.
Стандарт C не имеет понятия постоянной памяти, только объектов. const
не означает «постоянный», но является гарантией, данной программистом. Если вы хотите прострелить ногу, Си с радостью держит за вас пистолет.
Я внесу правку в свой пост, так как у меня недостаточно места в комментарии, но в основном в моей системе const char *ptr = "X"
приведет к тому, что *ptr
будет константой, но const char ptr[] = "X"
НЕ приведет к тому, что *ptr
будет считаться константой. Я думаю, что в моем конкретном случае я могу использовать этот метод (я всегда компилирую с оптимизацией и т. д.). @CraigEstey - если вы можете опубликовать это в качестве ответа, я его приму.
Я не думаю, что есть какой-либо разумный способ обеспечить это во время выполнения, по крайней мере, без оборудования, которое было бы на много порядков дороже, чем простой вызов strdup
.
Если функция должна принимать только строки неизменный в качестве аргументов (это слово, которое вы ищете - неизменяемый, в том смысле, что его время жизни будет остатком времени жизни процесса, а его содержимое не изменится до конца его время жизни), это должна быть задокументированная часть его интерфейсного контракта.
Спасибо. Я пришел к такому же выводу, но подумал, что спрошу, если у кого-нибудь есть какие-то хитрые уловки, которые я пропустил. К сожалению, я не доверяю будущим программистам читать комментарии. Я могу заставить всех использовать литералы вместо массивов const char, а затем использовать трюк с макросами ...
Вы можете определить свою функцию как макрос и сделать так, чтобы макрос преобразовал свой аргумент в строку перед передачей его функции. Тогда нет возможности передать нестроковый литерал без обхода макроса. ;-)
В зависимости от платформы проверка того, находится ли указатель в разделе данных только для чтения, намного дешевле, чем динамическое размещение.
@ M.M: Возможно, но это большое «если», и в нем много предостережений, если только вы не находитесь в простом случае со статической связью. При динамической компоновке и загрузке вам понадобится функция для проверки наличия новых разделяемых библиотек каждый раз, когда она вызывается, а это будет намного дороже, чем выделение. Кроме того, считается ли постоянная память в общих библиотеках неизменной (что действительно нужно OP), если библиотека была загружена во время выполнения и может быть выгружена через dlclose
? Я бы сказал нет, но дело в том, что определение даже неясно.
Проблема заключается не в постоянной памяти, а в статическом хранилище (например, я могу динамически выделять постоянную память с помощью
mmap
и выпускать ее позже). Не думаю, что это можно проверить.