2017-02-02 27 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无效,使得剩下的代码是容易出现内存违规行为。同时,我还假设微软在发布之前会检查这些例子。我在这里错过了很明显的东西吗在不再需要缓冲区后,是否有任何理由在这里使用CString,而不是简单地使用new,然后使用delete

+0

永远不要假设示例代码是无缺陷的。无论是来自微软还是其他人。通过使用示例代码,所有错误都是自动的。 – j6t

+0

我不这样做,因此我发布了这篇文章,但我希望微软在其API文档中提供的代码示例中有一个更好的标准,因为此代码通常被视为应该使用API​​的惯用方式。正如这里的其他答案所示,这段代码有很多错误。 –

+0

这不是Windows API文档。它是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. 一个新的变量)对话框调用,初始化为const wchar_t* current = fileName.GetString();后的代码替换的p所有剩余事件。

这是一个常见错误。每当开发人员认为他们需要各种类型的char*时,他们会忽略他们需要const char*,而几乎每个字符串类型都通过成员函数提供。


注意,还有其他错误示例代码,那些没有在这个答案是明确的处理(如字符类型的失配的另一 answer解释)。检索所选文件的列表


* A C++实现可以在这个answer找到。

+0

考虑到CFileDialog提供显式方法的“GetStartPosition”和“GetNextPathName”来提取所选文件的名称,整个pBufEnd也是无稽之谈。修复示例代码需要做的最重要的事情是将fileName.ReleaseBuffer移动到代码末尾,以停止无效的内存访问。显式调用GetBuffer和ReleaseBuffer也不是完全自动的内存管理。 –

+0

@ShaneMacLaughlin:为了使这段代码合法,需要很多修正,但是移动'ReleaseBuffer'调用(尽可能)并不是一个好的解决方案。我的回答概述了如何将“ReleaseBuffer”调用保留在原来的位置,并仍然使用指向受控序列的指针。 'GetBuffer' /'ReleaseBuffer'是与C库交互所必需的工件。在这些调用之间,内存管理确实是手动的,但是一旦'ReleaseBuffer'返回,您就可以恢复所有的自动内存管理。使用'CString'有优点,但示例大部分都是错误的。 – IInspectable

+0

如果您将ReleaseBuffer保留在原来的位置,然后继续使用p,则您正在访问理论上可能会根据文档改变的内存,但它不再有效。虽然在实践中它只会被CString的dtor重新分配或者通过改变CString的内容而被移除,但它似乎仍然是一种可行的方法。 –

2

您可能注意到规范和实现之间的区别。上面的代码工作是因为CString实现允许它,即使CString规范禁止它。

为了突出示例的质量:它混合了TCHARwchar_t。在tprintf("%s", start)字符串start必须是TCHAR*,但该示例使用wchar_t* start