Стоит задача. Прочитать строчку из реестра. Пусть для простоты HKEY_CURRENT_USER\Test. Вывести её в консоль. Казалось бы, что может быть проще?
char buf[20];
DWORD cb = sizeof(buf);
RegQueryValueEx(HKEY_CURRENT_USER, "Test", NULL, NULL, buf, &cb);
std::cout << buf << std::endl;
Упс. Мы только что ограничили себя 19-ю символами (+ завершающий нуль).
DWORD cb;
RegQueryValueEx(HKEY_CURRENT_USER, "Test", NULL, NULL, NULL, &cb);
char* buf = new char[cb];
RegQueryValueEx(HKEY_CURRENT_USER, "Test", NULL, NULL, buf, &cb);
std::cout << buf << std::endl;
Упс. Теперь нам надо будет где-то удалять этот указатель. А если exception вывалится?… Завернём-ка мы буфер в строку.
DWORD cb;
RegQueryValueEx(HKEY_CURRENT_USER, "Test", NULL, NULL, NULL, &cb); // 1
std::string buf;
buf.resize(cb);
RegQueryValueEx(HKEY_CURRENT_USER, "Test", NULL, NULL, (LPBYTE)&buf[0], &cb); // 2
std::cout << buf << std::endl;
Упс. Race condition. Между точками 1 и 2 значение в реестре поменялось параллельной программой. Причём в сторону увеличения. Оно уже не влезает в отведённый буфер. К счастью, функция RegQueryValueEx при этом не переполняет буфер, а, видя, что размер значения больше размера буфера, возвращает ERROR_MORE_DATA.
DWORD cb;
LONG res = RegQueryValueEx(HKEY_CURRENT_USER, "Test", NULL, NULL, NULL, &cb);
std::string buf;
while (ERROR_MORE_DATA == res)
{
buf.resize(cb);
res = RegQueryValueEx(HKEY_CURRENT_USER, "Test", NULL, NULL, (LPBYTE)&buf[0], &cb);
}
if (ERROR_SUCCESS != res)
{
std::cerr << "Ой, блин, фигня случилась\n";
return;
}
std::cout << buf << "\n";
Упс. Пока мы читали строку, она в реестре уменьшилась, и на консоль вместе со строкой вывалилась часть мусора, которая была в буфере. Там, правда, гарантированно нули (21.3.3/8), но всё равно неаккуратненько как-то.
DWORD cb;
LONG res = RegQueryValueEx(HKEY_CURRENT_USER, "Test", NULL, NULL, NULL, &cb);
std::string buf;
while (ERROR_MORE_DATA == res)
{
buf.resize(cb);
res = RegQueryValueEx(HKEY_CURRENT_USER, "Test", NULL, NULL, (LPBYTE)&buf[0], &cb);
}
if (ERROR_SUCCESS != res)
{
std::cerr << "Ой, блин, фигня случилась\n";
return;
}
buf.resize(cb);
std::cout << buf << "\n";
Упс. Всё равно один завершающий нуль печатается.
DWORD cb;
LONG res = RegQueryValueEx(HKEY_CURRENT_USER, "Test", NULL, NULL, NULL, &cb);
std::string buf;
while (ERROR_MORE_DATA == res)
{
buf.resize(cb);
res = RegQueryValueEx(HKEY_CURRENT_USER, "Test", NULL, NULL, (LPBYTE)&buf[0], &cb);
}
if (ERROR_SUCCESS != res)
{
std::cerr << "Ой, блин, фигня случилась\n";
return;
}
buf.resize(cb - 1);
std::cout << buf << "\n";
Упс. В реестре оказалась строчка без завершающего нуля. Такое тоже бывает. Последним -1 мы откусили последний символ.
DWORD cb;
LONG res = RegQueryValueEx(HKEY_CURRENT_USER, "Test", NULL, NULL, NULL, &cb);
std::string buf;
while (ERROR_MORE_DATA == res)
{
buf.resize(cb);
res = RegQueryValueEx(HKEY_CURRENT_USER, "Test", NULL, NULL, (LPBYTE)&buf[0], &cb);
}
if (ERROR_SUCCESS != res)
{
std::cerr << "Ой, блин, фигня случилась\n";
return;
}
buf.resize(cb);
if (0 == buf[cb - 1])
{
buf.resize(cb - 1);
}
std::cout << buf << "\n";
Упс. Теперь там и не строка вовсе, а REG_DWORD.
DWORD cb;
LONG res = RegQueryValueEx(HKEY_CURRENT_USER, "Test", NULL, NULL, NULL, &cb);
std::string buf;
DWORD valueType;
while (ERROR_MORE_DATA == res)
{
buf.resize(cb);
res = RegQueryValueEx(HKEY_CURRENT_USER, "Test", NULL, &valueType, (LPBYTE)&buf[0], &cb);
}
if (ERROR_SUCCESS != res)
{
std::cerr << "Ой, блин, фигня случилась\n";
return;
}
if (REG_SZ != valueType)
{
std::cerr << "Ну и что это нам такое дали? Это даже и не строка вовсе…\n";
return;
}
buf.resize(cb);
if (0 == buf[cb - 1])
{
buf.resize(cb - 1);
}
std::cout << buf << "\n";
Упс. Помимо REG_SZ, ещё бывают REG_EXPAND_SZ…
И, главное, реестр — это не единственное такое API. Их таких полно, где внутрь передаётся буфер и его размер, а потом, если буфера не хватает, то наружу уходит код ошибки и нужный (на данный момент) размер буфера. И нет никакой гарантии, что через три микросекунды не понадобится больше.
А заворачивая всё это в такой цикл, мы рискуем из него ооочень долго не выйти — если параллельная программа будет каждый раз вовремя удлинять строку на один символ