Обеспечить статическое хранение для параметра функции
У нас есть этот прототип функции:
BNode *b_new_node(const char *name, int pos, int len, const char *val);
Большая часть кода, использующего этот (и аналогичный) код, сгенерированный автоматически, и выглядит так:
b = b_new_node("foo.bar.id.1", 0, 10, some_data);
Функция выделяет новый BNode и копирует val
строка в него, но он просто присваивает name
член указателя, например
b_strlcpy(new_node->val, val, sizeof new_node->val);
new_node->name = name;
Это разрушает, если первый аргумент в b_new_node("foo.bar.id.1", 0, 10, some_data); не строковый литерал или что-то иное со статической продолжительностью хранения, а, например, буфер в стеке.
Есть ли в любом случае, с gcc (интерес представляют и другие компиляторы), мы можем проверить во время компиляции, что этот аргумент передается в статическом хранилище?
(Конечно, самый простой способ избежать этих возможных проблем - скопировать этот аргумент в узел - измерения, которые мы сделали с таким подходом, увеличивают потребность в памяти на 50% и замедляют программу на 10%, поэтому такой подход нежелателен).
3 ответа
Как правило, нет; нет C-средства, позволяющего узнать, указывает ли указатель на что-либо в статическом хранилище. Определенные среды и структуры данных могут изменить обстоятельства - например, проверить, находится ли указанный адрес в сегменте памяти только для чтения.
Экономия места за счет предотвращения дублирования
Для устранения дублированных значений, как указано name
Вы можете использовать шаблон flyweight, который не сильно отличается от интернирования строк.
По сути, вы строите центральный набор обнаруженных токенов и сохраняете только ссылку на каждый токен. Ссылки могут быть индексами массива или указателями.
Чтобы иметь возможность быстро выполнить очистку, вы могли бы объединить шаблон навески с подсчетом ссылок, где отсчет нуля означает отсутствие ссылок.
Чтобы поддерживать высокую производительность центрального хранилища, используйте структуру данных, где быстрый поиск, например набор, или карту, если используется подсчет ссылок.
Это обнаружит строковые литералы:
#include <stdio.h>
#define PRINT_IT(c) do {\
if (__builtin_constant_p(c))\
print_it(c, 1);\
else \
1/__builtin_constant_p(c);\
} while (0)
void print_it(const char *c, int is_static)
{
printf("%s is a constant %d\n", c, is_static);
}
int main(int argc, char *argv[])
{
char bar[] = "bar";
PRINT_IT("Foo"); //line 19
PRINT_IT(bar); //line 20
return 0;
}
$ gcc foo.c
foo.c: In function ‘main’:
foo.c:20: warning: division by zero
Таким образом, вы можете обернуть вашу функцию b_new_node() в макрос, возможно, просто для отладочной сборки, и использовать деление на ноль предупреждений.
Обратите внимание, что он определяет только строковые литералы как "аргументы", а не статическое хранилище, например
const char *foo = "foo";
PRINT_IT(foo); //will generate a warning
PRINT_IT("foo"); //will not generate a warning
PRINT_IT(global_foo); //will generate a warning, even when.
//global_foo is const char *foo = "foo"; or
//global_foo is const char foo[] = "foo";
Ты можешь сделать BNode
условно скопируйте название. Это потребует и дополнительной памяти в BNode
, Например:
typedef struct BNode {
char const* name;
unsigned char : own_name;
} BNode;
void b_copy_name(BNode* n) {
if(!n->own_name) {
char* p = strdup(n->name);
if(p) {
n->own_name = 1;
n->name = p;
}
else {
abort();
}
}
}
void b_destroy(BNode* n) {
// ...
if(n->own_name)
free(n->name);
}