2017-02-02 2 views
2

Я испытывал ряд случайных сбоев, используя класс MFC CFileDialog, поэтому я просмотрел их примерный код от this page, который гласит:Является ли этот пример Microsoft CFileDialog причиной потенциального нарушения памяти?

#define MAX_CFileDialog_FILE_COUNT 99 
#define FILE_LIST_BUFFER_SIZE ((MAX_CFileDialog_FILE_COUNT * (MAX_PATH + 1)) + 1) 

CString fileName; 
wchar_t* p = fileName.GetBuffer(FILE_LIST_BUFFER_SIZE); 
CFileDialog dlgFile(TRUE); 
OPENFILENAME& ofn = dlgFile.GetOFN(); 
ofn.Flags |= OFN_ALLOWMULTISELECT; 
ofn.lpstrFile = p; 
ofn.nMaxFile = FILE_LIST_BUFFER_SIZE; 

dlgFile.DoModal(); 
fileName.ReleaseBuffer(); 

wchar_t* pBufEnd = p + FILE_LIST_BUFFER_SIZE - 2; 
wchar_t* start = p; 
while((p < pBufEnd) && (*p)) 
    p++; 
if(p > start) 
{ 
    _tprintf(_T("Path to folder where files were selected: %s\r\n\r\n"), start); 
    p++; 

    int fileCount = 1; 
    while((p < pBufEnd) && (*p)) 
    { 
    start = p; 
    while((p < pBufEnd) && (*p)) 
     p++; 
    if(p > start) 
     _tprintf(_T("%2d. %s\r\n"), fileCount, start); 
    p++; 
    fileCount++; 
    } 
} 

По my reading этого, оператору fileName.ReleaseBuffer(); делает память указала в буфере переменной p недействительной, так что оставшийся код несет ответственность испытывать нарушение памяти. В то же время я также предполагаю, что Microsoft проверила такие примеры до публикации. Я пропустил что-то очевидное здесь? Есть ли причина для использования CString здесь через простой new, за которым следует delete после того, как буфер больше не требуется?

+0

Никогда не предполагайте, что код примера является ошибкой. Будь то от Microsoft или от кого-то другого. Используя пример кода, все ошибки будут автоматически за вами. – j6t

+0

Я этого не делаю, но я бы ожидал лучшего стандарта от Microsoft в примерах кода, которые поставляются с их документацией по API, поскольку этот код часто воспринимается как идиоматический способ использования API. Как показывают другие ответы здесь, в этом коде есть много чего не так. –

+0

Это не документация по API Windows. Это MFC, библиотека, которая всегда имела документацию, отвечающую более низким стандартам, чем документация по Windows API. Несмотря на это, можно надеяться, что документация MFC и образец кода были правильными. Если бы у меня была надежда, я бы опубликовал отчет о дефекте, чтобы вход был включен. Я недостаточно доверчив, чтобы сохранить эту надежду. – IInspectable

ответ

3

Образец кода не является официальной документацией. Этот образец неверен. documentation прав:

адрес, возвращаемый GetBuffer не может быть действительным после вызова ReleaseBuffer, поскольку дополнительные CSimpleStringT операции могут привести к CSimpleStringT буфера быть перераспределены.

Образец использует CString (над необработанными указателями и ручным управлением памятью) для автоматического управления памятью и безопасности исключений. Последнее намного сложнее получить с ручным управлением памятью (хотя этот образец также не имеет права на безопасность исключений).

Если вы хотите исправить код образца придерживаться контракта, следующие изменения должны быть сделаны: *

  1. Заменить wchar_t* pBufEnd = p + FILE_LIST_BUFFER_SIZE - 2; с const wchar_t* pBufEnd = fileName.GetString() + FILE_LIST_BUFFER_SIZE - 2;.
  2. Заменить wchar_t* start = p; с const wchar_t* start = fileName.GetString();
  3. Заменить все остальные вхождения p в коде после вызова диалога с новой переменной, инициализируется в const wchar_t* current = fileName.GetString();).

Это распространенная ошибка. Всякий раз, когда разработчик считает, что им нужны типы char*, они упускают из виду, что им нужен const char*, что почти каждый тип строки снабжается функцией-членом.


Обратите внимание, что есть и другие ошибки в коде образца, которые не были явно решаемые в этом ответе (например, несоответствие типов символов, как объяснено в другом answer).


* реализация C++, который извлекает список выбранных файлов можно найти в этом answer.

+0

Вся вещь pBufEnd - тоже бессмыслица, учитывая, что CFileDialog предоставляет явные методы GetStartPosition и GetNextPathName для извлечения имен выбранных файлов. Самое важное, что вам нужно сделать, чтобы исправить код примера, - это переместить файлName.ReleaseBuffer в конец кода, чтобы остановить недопустимый доступ к памяти. Вызов GetBuffer и ReleaseBuffer явно не является точно автоматическим управлением памятью. –

+0

@ShaneMacLaughlin: Существует множество исправлений, необходимых для того, чтобы этот код был законным, но перемещение вызова ReleaseBuffer (хотя это возможно) не является хорошим решением. Мой ответ показал, как оставить вызов ReleaseBuffer, где он есть, и по-прежнему использовать указатели в контролируемой последовательности. 'GetBuffer' /' ReleaseBuffer' являются артефактами, необходимыми для взаимодействия с библиотекой C. Между этими вызовами управление памятью действительно ручное, но как только 'ReleaseBuffer' вернется, вы получите все автоматическое управление памятью. Есть достоинство в использовании 'CString', но образец в основном ошибочен. – IInspectable

+0

, если вы оставите ReleaseBuffer там, где он есть, а затем продолжайте использовать p, вы получаете доступ к памяти, которая теоретически может измениться, поскольку в соответствии с документацией она больше недействительна. Хотя на практике это, скорее всего, будет освобождено от dStr CString или перемещено путем изменения содержимого CString, все же кажется ужасным способом приблизиться к этому. –

2

Возможно, вы заметили разницу между спецификацией и реализацией. Код выше работает, потому что реализация CString позволяет это, хотя спецификация CString запрещает ее.

И подчеркнуть качество примера: оно смешивает TCHAR и wchar_ttprintf("%s", start) строка start должна быть TCHAR*, но в этом примере используется wchar_t* start

Смежные вопросы