#c #uuid #idioms
#c #uuid #идиомы
Вопрос:
Существует ли идиоматический способ C резервировать и перерабатывать идентификаторы, которые гарантированно уникальны? Мои требования таковы:
- Предполагая, что существует идентификатор, который в данный момент не зарезервирован, reserve_id(void) возвращает мне этот идентификатор.
- В непрерывной последовательности вызовов reserve_id() ни один идентификатор не будет возвращен дважды
- Существует функция recycle(id_type), которая возвращает идентификатор доступному пулу.
Я, например, видел Boost::Uuid, но а) я не вижу документации, которая утверждает гарантированную уникальность двух UUID и б) на данный момент я ограничен более ранней версией Boost (1.40). Я мог бы нажать на обновление, если бы это было особенно идеально для задачи.
Комментарии:
1. Ваши требования неясны: как указано, вы могли бы
sprintf("id%d", id)
использовать счетчик статических идентификаторов, и это было бы хорошо для пары миллиардов значений, или возвращатьstd::string
after= 'x'
;-). Если на практике значения не исчерпаны, переработка может быть невозможной, или вы могли бы создать deque<> и использовать его предпочтительно. Какие еще ограничения / ожидания у вас действительно есть … по длине, формату, производительности, уникальности между хостами, уникальности при выполнении разных процессов и т.д.? Или мы должны выводить требования, аналогичные назначению Boost::Uuid?2. Чтобы было понятно, я беспокоюсь о целочисленном обтекании, поскольку я пишу долго работающий сервер. Моя главная проблема — # 2 выше.
3. Вы могли бы легко использовать 64-разрядную систему без знака
int
или даже цепочку 2 с простымif (! low_order) high_order;
…? Это должно быть нормально, пока солнце не сгорит….4. Стоит подумать о том, что означает «крайне маловероятное» дублирование. википедия описывает вероятность случайного 122-битного UUID «Другими словами, только после генерации 1 миллиарда UUID каждую секунду в течение следующих 100 лет вероятность создания только одного дубликата составит около 50%. Вероятность одного дубликата составляла бы около 50%, если бы каждый человек на земле владел 600 миллионами UUID.» en.wikipedia.org/wiki/Universally_unique_identifier . ИМО, более вероятно, что аппаратный сбой какого-либо типа предоставит вам дубликат в любом тщательно разработанном коде, который вы используете.
5. @Майкл Андерсон: Я серьезно надеюсь, что вы не отвечаете за проектирование самолетов, автомобилей или ядерных устройств 🙂
Ответ №1:
Я думаю, вы уже решили эту проблему для большинства практических целей, найдя Boost::Uuid , за исключением вашего требования перерабатывать уже сгенерированные идентификаторы.
Из документации, на которую вы ссылались в вопросе:
Когда идентификаторы UUID генерируются одним из определенных механизмов, они либо гарантированно уникальны, отличаются от всех других сгенерированных идентификаторов UUID (то есть они никогда не генерировались ранее и никогда не будут сгенерированы снова), либо с высокой вероятностью будут уникальными (в зависимости от механизма).
Если вы одержимы переработкой и повторным использованием существующих идентификаторов, я полагаю, вы могли бы поддерживать создание пула UUID с течением времени, генерируя новые только тогда, когда вам это нужно, и обнаруживая, что пул пуст. Но я не могу представить сценарий, в котором это было бы предпочтительнее генерации нового UUID.
РЕДАКТИРОВАТЬ: Вы отметили, что вам нужна гарантия уникальности. На самом деле, вы никогда не получите его при программной генерации уникального идентификатора. На практике вы собираетесь сохранить сгенерированный идентификатор в типе данных, который имеет конечный размер, и поэтому возможный набор идентификаторов, которые вы можете сгенерировать, тоже конечен. ИМХО, лучшее, чего вы можете достичь, — это имитировать уникальность в пределах порога допуска.
Вы могли бы сделать это с помощью
-
Использование метода, который делает шансы на получение дубликата UUID очень незначительными (это то, что будет делать Boost::UUID);
-
Обертывание генерации UUID с высокой вероятностью уникальности в какой-либо другой логике, которая ищет вновь сгенерированный UUID в списке уже сгенерированных UUID, чтобы исключить тот крошечный шанс, что новый UUID является дубликатом. Очевидно, что практичность выполнения этого уменьшается по мере приближения к очень большому количеству UUID в вашем списке. Сколько вы ожидаете создать?
-
Если вам нужно действительно огромное количество уникальных идентификаторов, больше, чем поместилось бы в собственный тип, вы могли бы реализовать тип, который управляет памятью и выполняет необходимую математику, и просто создавать последовательные идентификаторы, или вы могли бы, возможно, использовать что-то вроде библиотеки GNU Bignum, чтобы сделать это за вас.
Комментарии:
1. Так что «чрезвычайно вероятно» в данном случае для меня не работает. Я действительно хочу гарантию.
2. @Andres Jaan Тэк: какого рода гарантия? Нужна ли вам более надежная гарантия от вашего генератора UUID, что числа уникальны, чем вы получаете из своей оперативной памяти, что она сохранит их правильно, в отличие от переворачивания нескольких битов, чтобы изменить новое уникальное значение на значение, которое вы видели раньше? Выполняется ли этот код на реальной машине или платоновский идеал машины Тьюринга?
3. @razlebe Мне нравится ваше отношение к этой проблеме. Подводя итог, правильный ответ таков: «Все рады верить генератору UUID, и таким образом все было в порядке».
Ответ №2:
Как долго живут идентификаторы? Вам ДЕЙСТВИТЕЛЬНО нужно их перерабатывать или вы можете жить с тем, что они будут уникальными вечно? Сколько вам нужно сгенерировать всех сразу? Сколько битов вы можете выделить для идентификатора?
Вот простой рецепт: возьмите mac-адрес вашей карты Ethernet (который является уникальным во всем мире, исключая аппаратные проблемы), добавьте время / дату (с разрешением в миллисекунду) и счетчик увеличивающихся целых чисел (увеличивается один раз на каждый сгенерированный идентификатор), и у вас будет идентификатор, уникальный в пределах вашего диапазона времени / дат, при условии, что вы не генерируете MAXINT из них за одну миллисекунду на этом компьютере. Теперь это НЕ выглядит случайным образом, и злоумышленнику легко предсказать, поэтому не используйте его для обеспечения безопасности, и это, конечно, не самое эффективное использование битов, но оно уникально во всем мире.
Комментарии:
1. То, что я описал, не является защитой от ЛЮБОГО типа преднамеренной атаки. Счетчик целых чисел защитит от обычных проблем со сменой часов, если только вы не будете прокручивать его много раз в день.
2. да, то, что я сказал, не было задумано как критика. Просто кое-что, что следует иметь в виду при использовании времени.
Ответ №3:
Какого рода уникальность вам требуется?
Просто уникальные на протяжении всего срока службы программы или уникальные при нескольких запусках / межпроцессном взаимодействии?
Если это первое, то вы могли бы просто new
выделить байт памяти, а затем использовать адрес этой памяти в качестве своего идентификатора. Это будет гарантированно уникальным до тех пор, пока вы delete
не освободите память, после чего она может быть переработана.
Это можно легко обернуть в класс, подобный этому:
#include <stdint.h>
class UID
{
public:
typedef uint64_t id_type;
static const id_type reserve_id()
{
uint8_t* idBlock = new uint8_t;
*idBlock = validId;
return (id_type)idBlock;
}
static void recycle(id_type id)
{
uint8_t* idBlock = (uint8_t*)id;
if (*idBlock == validId)
{
*idBlock = 0;
delete idBlock;
}
}
private:
static const uint8_t validId = 0x1D;
};
Возможно, это немного необычно, но это соответствует вашим требованиям, если вам нужна уникальность только для каждого процесса 🙂
Ответ №4:
Да, это просто.
reserve_id
Функция являетсяoperator new(0)
.- При этом выделяется ноль байт, но с уникальным адресом.
recycle
Функция, конечноoperator delete
Комментарии:
1. @Salters Верно — до того момента, когда программное обеспечение будет перезапущено или у вас закончатся адреса.
2. разве это не то, что я предложил за 2 часа до вас? (За исключением того, что я выбрал выделение в один байт, чтобы добавить некоторую ограниченную проверку). Где мой голос, черт возьми? 🙂
3. Ну, и я не стал утруждать себя приведением к целочисленному типу. Если UUID приемлемы,
void*
вероятно, это тоже так.
Ответ №5:
Проблема, похоже, не связана с C , это скорее фундаментальная проблема. Ожидается, что сколько идентификаторов будут действительными в любой момент времени? Если вы ожидаете, что в любой момент времени у вас будет несколько допустимых идентификаторов, просто поместите их в контейнер, такой как связанный список, вектор или набор, в зависимости от ваших требований к производительности и относительной частоты повторного использования / резервирования. Вероятно, лучшим вариантом является отсортированный связанный список, поскольку у вас будут операции как по переработке, так и по резервированию в O (n). Вектор имеет O (n), O (n log n), а набор имеет O (n log n), O (n) соответственно (может быть, я ошибаюсь, я очень быстро сообразил).
void recycle(ID) {
container.remove(ID);
// abort if unsuccessiful (= invalid ID)
}
ID reserve() {
static ID last = 0;
while(container.find(last)) {
last ;
}
return last;
}