Centaur
17:45 20-10-2004 [Win32] Прочитать строчку из реестра
Стоит задача. Прочитать строчку из реестра. Пусть для простоты 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. Их таких полно, где внутрь передаётся буфер и его размер, а потом, если буфера не хватает, то наружу уходит код ошибки и нужный (на данный момент) размер буфера. И нет никакой гарантии, что через три микросекунды не понадобится больше.

А заворачивая всё это в такой цикл, мы рискуем из него ооочень долго не выйти — если параллельная программа будет каждый раз вовремя удлинять строку на один символ
Комментарии:
Centaur
18:01 20-10-2004
Что забавно, берём модуль Registry из Delphi 5. Там написано буквально следующее:
function TRegistry.ReadString(const Name: string): string;
var
  Len: Integer;
  RegData: TRegDataType;
begin
  Len := GetDataSize(Name);
  if Len > 0 then
  begin
    SetString(Result, nil, Len);
    GetData(Name, PChar(Result), Len, RegData);
    if (RegData = rdString) or (RegData = rdExpandString) then
      SetLength(Result, StrLen(PChar(Result)))
    else ReadError(Name);
  end
  else Result := '';
end;

Упс. Race condition И, к тому же, если строчка случайно оказалась REG_EXPAND_SZ, то прикладной программист об этом не узнает. И если в строчке были нулевые символы, то StrLen найдёт первый из них, а SetLength обрежет строку по этому месту. А если строка была не завершённая нулём, то мы встряли — buffer overrun