一个跨平台的 C++ 内存泄漏检测器

From:http://www.ibm.com/developerworks/cn/linux/l-mleak2/index.html

内存泄漏对于C/C++程序员来说也可以算作是个永恒的话题了吧。在Windows下,MFC的一个很有用的功能就是能在程序运行结束时报告是否发生了内存泄漏。在Linux下,相对来说就没有那么容易使用的解决方案了:像mpatrol之类的现有工具,易用性、附加开销和性能都不是很理想。本文实现一个极易于使用、跨平台的C++内存泄漏检测器。并对相关的技术问题作一下探讨。

吴咏炜 (adah@netstd.com), 系统工程师

2004 年 3 月 01 日

  • +内容

基本使用

对于下面这样的一个简单程序test.cpp:

int main()
{int* p1 = new int;char* p2 = new char[10];return 0;
}

我们的基本需求当然是对于该程序报告存在两处内存泄漏。要做到这点的话,非常简单,只要把debug_new.cpp也编译、链接进去就可以了。在Linux下,我们使用:

g++ test.cpp debug_new.cpp -o test

输出结果如下所示:

Leaked object at 0x805e438 (size 10, <Unknown>:0)
Leaked object at 0x805e410 (size 4, <Unknown>:0)

如果我们需要更清晰的报告,也很简单,在test.cpp开头加一行

#include "debug_new.h"

即可。添加该行后的输出如下:

Leaked object at 0x805e438 (size 10, test.cpp:5)
Leaked object at 0x805e410 (size 4, test.cpp:4)

非常简单!

背景知识

在new/delete操作中,C++为用户产生了对operator new和operator delete的调用。这是用户不能改变的。operator new和operator delete的原型如下所示:

void *operator new(size_t) throw(std::bad_alloc);
void *operator new[](size_t) throw(std::bad_alloc);
void operator delete(void*) throw();
void operator delete[](void*) throw();

对于"new int",编译器会产生一个调用"operator new(sizeof(int))",而对于"new char[10]",编译器会产生"operator new[](sizeof(char) * 10)"(如果new后面跟的是一个类名的话,当然还要调用该类的构造函数)。类似地,对于"delete ptr"和"delete[] ptr",编译器会产生"operator delete(ptr)"调用和"operator delete[](ptr)"调用(如果ptr的类型是指向对象的指针的话,那在operator delete之前还要调用对象的析构函数)。当用户没有提供这些操作符时,编译系统自动提供其定义;而当用户自己提供了这些操作符时,就覆盖了编译系统提供的版本,从而可获得对动态内存分配操作的精确跟踪和控制。

同时,我们还可以使用placement new操作符来调整operator new的行为。所谓placement new,是指带有附加参数的new操作符,比如,当我们提供了一个原型为

void* operator new(size_t size, const char* file, int line);

的操作符时,我们就可以使用"new("hello", 123) int"来产生一个调用"operator new(sizeof(int), "hello", 123)"。这可以是相当灵活的。又如,C++标准要求编译器提供的一个placement new操作符是

void* operator new(size_t size, const std::nothrow_t&);

其中,nothrow_t通常是一个空结构(定义为"struct nothrow_t {};"),其唯一目的是提供编译器一个可根据重载规则识别具体调用的类型。用户一般简单地使用"new(std::nothrow) 类型"(nothrow是一个nothrow_t类型的常量)来调用这个placement new操作符。它与标准new的区别是,new在分配内存失败时会抛出异常,而"new(std::nothrow)"在分配内存失败时会返回一个空指针。

要注意的是,没有对应的"delete(std::nothrow) ptr"的语法;不过后文会提到另一个相关问题。

要进一步了解以上关于C++语言特性的信息,请参阅[Stroustrup1997],特别是6.2.6、10.4.11、15.6、19.4.5和B.3.4节。这些C++语言特性是理解本实现的关键。

检测原理

和其它一些内存泄漏检测的方式类似,debug_new中提供了operator new重载,并使用了宏在用户程序中进行替换。debug_new.h中的相关部分如下:

void* operator new(size_t size, const char* file, int line);
void* operator new[](size_t size, const char* file, int line);
#define new DEBUG_NEW
#define DEBUG_NEW new(__FILE__, __LINE__)

拿上面加入debug_new.h包含后的test.cpp来说,"new char[10]"在预处理后会变成"new("test.cpp", 4) char[10]",编译器会据此产生一个"operator new[](sizeof(char) * 10, "test.cpp", 4)"调用。通过在debug_new.cpp中自定义"operator new(size_t, const char*, int)"和"operator delete(void*)"(以及"operator new[]…"和"operator delete[]…";为避免行文累赘,以下不特别指出,说到operator new和operator delete均同时包含数组版本),我可以跟踪所有的内存分配调用,并在指定的检查点上对不匹配的new和delete操作进行报警。实现可以相当简单,用map记录所有分配的内存指针就可以了:new时往map里加一个指针及其对应的信息,delete时删除指针及对应的信息;delete时如果map里不存在该指针为错误删除;程序退出时如果map里还存在未删除的指针则说明有内存泄漏。

不过,如果不包含debug_new.h,这种方法就起不了作用了。不仅如此,部分文件包含debug_new.h,部分不包含debug_new.h都是不可行的。因为虽然我们使用了两种不同的operator new --"operator new(size_t, const char*, int)"和"operator new(size_t)"-- 但可用的"operator delete"还是只有一种!使用我们自定义的"operator delete",当我们删除由"operator new(size_t)"分配的指针时,程序将认为被删除的是一个非法指针!我们处于一个两难境地:要么对这种情况产生误报,要么对重复删除同一指针两次不予报警:都不是可接受的良好行为。

看来,自定义全局"operator new(size_t)"也是不可避免的了。在debug_new中,我是这样做的:

void* operator new(size_t size)
{return operator new(size, "<Unknown>", 0);
}

但前面描述的方式去实现内存泄漏检测器,在某些C++的实现中(如GCC 2.95.3中带的SGI STL)工作正常,但在另外一些实现中会莫名其妙地崩溃。原因也不复杂,SGI STL使用了内存池,一次分配一大片内存,因而使利用map成为可能;但在其他的实现可能没这样做,在map中添加数据会调用operator new,而operator new会在map中添加数据,从而构成一个死循环,导致内存溢出,应用程序立即崩溃。因此,我们不得不停止使用方便的STL模板,而使用手工构建的数据结构:

struct new_ptr_list_t
{new_ptr_list_t*		next;const char*			file;int					line;size_t				size;
};

我最初的实现方法就是每次在使用new分配内存时,调用malloc多分配 sizeof(new_ptr_list_t) 个字节,把分配的内存全部串成一个一个链表(利用next字段),把文件名、行号、对象大小信息分别存入file、line和size字段中,然后返回(malloc返回的指针 + sizeof(new_ptr_list_t))。在delete时,则在链表中搜索,如果找到的话((char*)链表指针 + sizeof(new_ptr_list_t) == 待释放的指针),则调整链表、释放内存,找不到的话报告删除非法指针并abort。

至于自动检测内存泄漏,我的做法是生成一个静态全局对象(根据C++的对象生命期,在程序初始化时会调用该对象的构造函数,在其退出时会调用该对象的析构函数),在其析构函数中调用检测内存泄漏的函数。用户手工调用内存泄漏检测函数当然也是可以的。

基本实现大体就是如此。

可用性改进

上述方案最初工作得相当好,直到我开始创建大量的对象为止。由于每次delete时需要在链表中进行搜索,平均搜索次数为(链表长度/2),程序很快就慢得像乌龟爬。虽说只是用于调试,速度太慢也是不能接受的。因此,我做了一个小更改,把指向链表头部的new_ptr_list改成了一个数组,一个对象指针放在哪一个链表中则由它的哈希值决定。--用户可以更改宏DEBUG_NEW_HASH和DEBUG_NEW_HASHTABLESIZE的定义来调整debug_new的行为。他们的当前值是我测试下来比较满意的定义。

使用中我们发现,在某些特殊情况下(请直接参看debug_new.cpp中关于DEBUG_NEW_FILENAME_LEN部分的注释),文件名指针会失效。因此,目前的debug_new的缺省行为会复制文件名的头20个字符,而不只是存储文件名的指针。另外,请注意原先new_ptr_list_t的长度为16字节,现在是32字节,都能保证在通常情况下内存对齐。

此外,为了允许程序能和 new(std::nothrow) 一起工作,我也重载了operator new(size_t, const std::nothrow_t&) throw();不然的话,debug_new会认为对应于 new(nothrow) 的delete调用删除的是一个非法指针。由于debug_new不抛出异常(内存不足时程序直接报警退出),所以这一重载的操作只不过是调用 operator new(size_t) 而已。这就不用多说了。

前面已经提到,要得到精确的内存泄漏检测报告,可以在文件开头包含"debug_new.h"。我的惯常做法可以用作参考:

#ifdef _DEBUG
#include "debug_new.h"
#endif

包含的位置应当尽可能早,除非跟系统的头文件(典型情况是STL的头文件)发生了冲突。在某些情况下,可能会不希望debug_new重定义new,这时可以在包含debug_new.h之前定义DEBUG_NEW_NO_NEW_REDEFINITION,这样的话,在用户应用程序中应使用debug_new来代替new(顺便提一句,没有定义DEBUG_NEW_NO_NEW_REDEFINITION时也可以使用debug_new代替new)。在源文件中也许就该这样写:

#ifdef _DEBUG
#define DEBUG_NEW_NO_NEW_REDEFINITION
#include "debug_new.h"
#else
#define debug_new new
#endif

并在需要追踪内存分配的时候全部使用debug_new(考虑使用全局替换)。

用户可以选择定义DEBUG_NEW_EMULATE_MALLOC,这样debug_new.h会使用debug_new和delete来模拟malloc和free操作,使得用户程序中的malloc和free操作也可以被跟踪。在使用某些编译器的时候(如Digital Mars C++ Compiler 8.29和Borland C++ Compiler 5.5.1),用户必须定义NO_PLACEMENT_DELETE,否则编译无法通过。用户还可以使用两个全局布尔量来调整debug_new的行为:new_verbose_flag,缺省为false,定义为true时能在每次new/delete时向标准错误输出显示跟踪信息;new_autocheck_flag,缺省为true,即在程序退出时自动调用check_leaks检查内存泄漏,改为false的话用户必须手工调用check_leaks来检查内存泄漏。

需要注意的一点是,由于自动调用check_leaks是在debug_new.cpp中的静态对象析构时,因此不能保证用户的全局对象的析构操作发生在check_leaks调用之前。对于Windows上的MSVC,我使用了"#pragma init_seg(lib)"来调整对象分配释放的顺序,但很遗憾,我不知道在其他的一些编译器中(特别是,我没能成功地在GCC中解决这一问题)怎么做到这一点。为了减少误报警,我采取的方式是在自动调用了check_leaks之后设new_verbose_flag为true;这样,就算误报告了内存泄漏,随后的delete操作还是会被打印显示出来。只要泄漏报告和delete报告的内容一致,我们仍可以判断出没有发生内存泄漏。

Debug_new也能检测对同一指针重复调用delete(或delete无效指针)的错误。程序将显示错误的指针值,并强制调用abort退出。

还有一个问题是异常处理。这值得用专门的一节来进行说明。

构造函数中的异常

我们看一下以下的简单程序示例:

#include <stdexcept>
#include <stdio.h>
void* operator new(size_t size, int line)
{printf("Allocate %u bytes on line %d\\n", size, line);return operator new(size);
}
class Obj {
public:Obj(int n);
private:int _n;
};
Obj::Obj(int n) : _n(n)
{if (n == 0) {throw std::runtime_error("0 not allowed");}
}
int main()
{try {Obj* p = new(__LINE__) Obj(0);delete p;} catch (const std::runtime_error& e) {printf("Exception: %s\\n", e.what());}
}

看出代码中有什么问题了吗?实际上,如果我们用MSVC编译的话,编译器的警告信息已经告诉我们发生了什么:

test.cpp(27) : warning C4291: 'void *__cdecl operator new(unsigned int,int)' : 
no matching operator delete found; memory will not be freed if initialization throwsan exception

好,把debug_new.cpp链接进去。运行结果如下:

Allocate 4 bytes on line 27 Exception: 0 not allowed Leaked object at 00342BE8 (size 4, <Unknown>:0)

啊哦,内存泄漏了不是!

当然,这种情况并非很常见。可是,随着对象越来越复杂,谁能够保证一个对象的子对象的构造函数或者一个对象在构造函数中调用的所有函数都不会抛出异常?并且,解决该问题的方法并不复杂,只是需要编译器对 C++ 标准有较好支持,允许用户定义 placement delete 算符([C++1998],5.3.4节;网上可以找到1996年的标准草案,比如下面的网址 http://www.comnets.rwth-aachen.de/doc/c++std/expr.html#expr.new)。在我测试的编译器中,GCC(2.95.3或更高版本,Linux/Windows)和MSVC(6.0或更高版本)没有问题,而Borland C++ Compiler 5.5.1和Digital Mars C++ Compiler(到v8.38为止的所有版本)则不支持该项特性。在上面的例子中,如果编译器支持的话,我们就需要声明并实现 operator delete(void*, int) 来回收new分配的内存。编译器不支持的话,需要使用宏让编译器忽略相关的声明和实现。如果要让debug_new在Borland C++ Compiler 5.5.1或Digital Mars C++ Compiler下编译的话,用户必须定义宏NO_PLACEMENT_DELETE;当然,用户得自己注意小心构造函数中抛出异常这个问题了。

方案比较

IBM developerWorks上刊载了洪琨先生设计实现的一个Linux上的内存泄漏检测方法([洪琨2003])。我的方案与其相比,主要区别如下:

优点:

  • 跨平台:只使用标准函数,并且在GCC 2.95.3/3.2(Linux/Windows)、MSVC 6、Digital Mars C++ 8.29、Borland C++ 5.5.1等多个编译器下调试通过。(虽然Linux是我的主要开发平台,但我发现,有时候能在Windows下编译运行代码还是非常方便的。)
  • 易用性:由于重载了operator new(size_t)--洪琨先生只重载了operator new(size_t, const char*, int)--即使不包含我的头文件也能检测内存泄漏;程序退出时能自动检测内存泄漏;可以检测用户程序(不包括系统/库文件)中malloc/free产生的内存泄漏。
  • 灵活性:有多个灵活的可配置项,可使用宏定义进行编译时选择。
  • 可重入性:不使用全局变量,没有嵌套delete问题。
  • 异常安全性:在编译器支持的情况下,能够处理构造函数中抛出的异常而不发生内存泄漏。

缺点:

  • 单线程模型:跨平台的多线程实现较为麻烦,根据项目的实际需要,也为了代码清晰简单起见,我的方案不是线程安全的;换句话说,如果多个线程中同时进行new或delete操作的话,后果未定义。
  • 未实现运行中内存泄漏检测报告机制:没有遇到这个需求J;不过,如果要手工调用check_leaks函数实现的话也不困难,只是跨平台性就有点问题了。
  • 不能检测带 [] 算符和不带 [] 算符混用的不匹配:主要也是需求问题(如果要修改实现的话并不困难)。
  • 不能在错误的delete调用时显示文件名和行号:应该不是大问题;由于我重载了operator new(size_t),可以保证delete出错时程序必然有问题,因而我不只是显示警告信息,而且会强制程序abort,可以通过跟踪程序、检查abort时程序的调用栈知道问题出在哪儿。

另外,现在已存在不少商业和Open Source的内存泄漏检测器,本文不打算一一再做比较。Debug_new与它们相比,功能上总的来说仍较弱,但是,其良好的易用性和跨平台性、低廉的附加开销还是具有很大优势的。

总结和讨论

以上段落基本上已经说明了debug_new的主要特点。下面做一个小小的总结。

重载的算符:

  • operator new(size_t, const char*, int)
  • operator new[](size_t, const char*, int)
  • operator new(size_t)
  • operator new[](size_t)
  • operator new(size_t, const std::nothrow_t&)
  • operator new[](size_t, const std::nothrow_t&)
  • operator delete(void*)
  • operator delete[](void*)
  • operator delete(void*, const char*, int)
  • operator delete[](void*, const char*, int)
  • operator delete(void*, const std::nothrow_t&)
  • operator delete[](void*, const std::nothrow_t&)

提供的函数:

  • check_leaks() 
    检查是否发生内存泄漏

提供的全局变量

  • new_verbose_flag 
    是否在new和delete时"罗嗦"地显示信息
  • new_autocheck_flag 
    是否在程序退出是自动检测一次内存泄漏

可重定义的宏:

  • NO_PLACEMENT_DELETE 
    假设编译器不支持placement delete(全局有效)
  • DEBUG_NEW_NO_NEW_REDEFINITION 
    不重定义new,假设用户会自己使用debug_new(包含debug_new.h时有效)
  • DEBUG_NEW_EMULATE_MALLOC 
    重定义malloc/free,使用new/delete进行模拟(包含debug_new.h时有效)
  • DEBUG_NEW_HASH 
    改变内存块链表哈希值的算法(编译debug_new.cpp时有效)
  • DEBUG_NEW_HASHTABLE_SIZE 
    改变内存块链表哈希桶的大小(编译debug_new.cpp时有效)
  • DEBUG_NEW_FILENAME_LEN 
    如果在分配内存时复制文件名的话,保留的文件名长度;为0时则自动定义DEBUG_NEW_NO_FILENAME_COPY(编译debug_new.cpp时有效;参见文件中的注释)
  • DEBUG_NEW_NO_FILENAME_COPY 
    分配内存时不进行文件名复制,而只是保存其指针;效率较高(编译debug_new.cpp时有效;参见文件中的注释)

我本人认为,debug_new目前的一个主要缺陷是不支持多线程。对于某一特定平台,要加入多线程支持并不困难,难就难在通用上(当然,条件编译是一个办法,虽然不够优雅)。等到C++标准中包含线程模型时,这个问题也许能比较完美地解决吧。另一个办法是使用像boost这样的程序库中的线程封装类,不过,这又会增加对其它库的依赖性--毕竟boost并不是C++标准的一部分。如果项目本身并不用boost,单为了这一个目的使用另外一个程序库似乎并不值得。因此,我自己暂时就不做这进一步的改进了。

另外一个可能的修改是保留标准operator new的异常行为,使其在内存不足的情况下抛出异常(普通情况)或是返回NULL(nothrow情况),而不是像现在一样终止程序运行(参见debug_new.cpp的源代码)。这一做法的难度主要在于后者:我没想出什么方法,可以保留 new(nothrow) 的语法,同时能够报告文件名和行号,并且还能够使用普通的new。不过,如果不使用标准语法,一律使用debug_new和debug_new_nothrow的话,那还是非常容易实现的。

如果大家有改进意见或其它想法的话,欢迎来信讨论。

debug_new 的源代码目前可以在 dbg_new.zip处下载。

在这篇文章的写完之后,我终于还是实现了一个线程安全的版本。该版本使用了一个轻量级的跨平台互斥体类fast_mutex(目前支持Win32和POSIX线程,在使用GCC(Linux/MinGW)、MSVC时能通过命令行参数自动检测线程类型)。有兴趣的话可在http://mywebpage.netscape.com/yongweiwu/dbg_new.tgz下载。

参考资料

[C++1998] ISO/IEC 14882. Programming Languages-C++, 1st Edition. International Standardization Organization, International Electrotechnical Commission, American National Standards Institute, and Information Technology Industry Council, 1998

[Stroustrup1997] Bjarne Stroustrup. The C++ Programming Language, 3rd Edition. Addison-Wesley, 1997

[洪琨2003] 洪琨。 《如何在 linux 下检测内存泄漏》,IBM developerWorks 中国网站。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/404036.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PAT 1039. 到底买不买(20)

题目链接&#xff1a;https://www.patest.cn/contests/pat-b-practise/1039 解题思路&#xff1a;自己刚开始想的是用两个字符串来做&#xff0c;搜别人题解的时候发现别人一种很好的解题思路&#xff0c;是用string做的&#xff0c;感觉做的很巧妙 具体的代码如下&#xff1a;…

ios UIPickerView 技巧集锦

重新实现 UIPickerView 参考资料: http://www.cocoachina.com/bbs/read.php?tid85374 http://www.cocoachina.com/iphonedev/toolthain/2011/1205/3663.html 设置循环滚动 设置 UIPickerView 的数据源数量为很大的规模, 取数据时对行数进行取模, 从而实现循环滚动的效果. 在每…

Delphi工具之TDUMP

TDUMP是一个命令行程序&#xff0c;它输出.exe或.dll&#xff08;以及其他文件类型&#xff09;文件的结构。 TDUMP位于Delphi安装目录下的Bin目录里&#xff0c;如下&#xff1a; 缺省时&#xff0c;TDUMP的输出显示到屏幕上&#xff1b; 用户可以将TDUMP的输出定向到一个文本…

libevent的使用(socket)

From: http://blog.csdn.net/kaitiren/article/details/35253319 这篇文章介绍下libevent在socket异步编程中的应用。在一些对性能要求较高的网络应用程序中&#xff0c;为了防止程序阻塞在socket I/O操作上造成程序性能的下降&#xff0c;需要使用异步编程&#xff0c;即程序…

消费者承担消费税真的吃亏了吗?

像小老鼠一样享受&#xff0c;才不管消费税呢其实&#xff0c;我本来对经济学不感兴趣。一次偶然的机会&#xff0c;我在朋友的寝室里看到了传说中经济学最经典的教材之一——曼昆&#xff08;Mankiw&#xff09;的《经济学原理》。好奇心驱使我随手翻开了一页&#xff0c;读了…

cocos2dx libevent简介和使用

From: http://blog.csdn.net/kaitiren/article/details/35254577 libevent是一个基于事件触发的网络库&#xff0c;memcached底层也是使用libevent库&#xff0c;今天学习下。总体来说&#xff0c;libevent有下面一些特点和优势&#xff1a;* 统一数据源&#xff0c; 统一I/O事…

Linux查看主板的相关信息

一条命令就能知道主板的一些信息&#xff0c;具体的内容就无需解释了&#xff0c;诸如厂商啊什么的&#xff0c;英文词的借助Google吧&#xff0c;哈哈 转载于:https://blog.51cto.com/kumu1988/1086248

在mac上配置cocos2d-x开发环境

From: http://www.cnblogs.com/xiaodao/archive/2013/01/08/2850751.html 一、下载cocos2d-x最新安装包 在终端中cd到本地将要存放目录&#xff0c;执行git命令 git clone https://github.com/cocos2d/cocos2d-x.git 二、如果开发ios程序&#xff0c;需要配置xcode模板 下好…

ASP.NET笔记(三)

一、使用主题自定义网站&#xff08;App_Themes&#xff0c;<Page Theme/StyleSheetTheme..>&#xff0c;<page theme"">&#xff09; 创建主题并将其应用于页 在 Visual Web Developer 中&#xff0c;右击网站名&#xff0c;单击“添加 ASP.Net 文件夹”…

磁盘IO:缓存IO与直接IO

文件系统IO分为DirectIO和BufferIO,其中BufferIO也叫Normal IO。 1. 缓存IO 缓存I/O又被称作标准I/O&#xff0c;大多数文件系统的默认I/O操作都是缓存I/O。在Linux的缓存I/O机制中&#xff0c;数据先从磁盘复制到内核空间的缓冲区&#xff0c;然后从内核空间缓冲区复制到应用程…

F#初学笔记08

惰性求值惰性求值也叫按需调用&#xff0c;是一个演算策略&#xff0c;延迟一个表达式的演算&#xff0c;直到需要用到演算值的时候再演算&#xff0c;同时也避免了重复演算。惰性求值的好处包括&#xff1a;避免不必要的计算&#xff0c;提升性能。可以构造以构造一个无限的数…

在Finder标题栏上显示完整路径

From: http://www.7do.net/resources-5411-1-1.html 打开终端&#xff0c;输入以下命令并回车&#xff1a; defaults write com.apple.finder _FXShowPosixPathInTitle -bool YES 然后再把finder关了再打开&#xff0c;你会发现路径栏变成这个样子了&#xff1a; 其实呢&a…

Wamp5 配置PHP 图文详解(转)

Wamp5论坛配置图文版 知识扫盲&#xff1a; 1、WampSever指的是apache mySQL PHP三合一套装&#xff0c;第一字母W&#xff0c;是指用于windows系统&#xff0c;我用的是2.0f版。用于Linux系统的&#xff0c;是LampSever&#xff0c;第一字母是L。 下载地址http://jaist.dl.s…

9个小窍门让OS X中Finder用起来更顺手

From: http://digi.tech.qq.com/a/20130309/000051.htm 腾讯数码讯&#xff08;编译&#xff1a; 李斯特&#xff09;Finder是OS X系统上用户与文件系统打交道的主要途径之一&#xff0c;它的默认设置是能满足普通用户绝大多数日常需求的。但我们同样可以通过一些小配置来使它…

xcode-select: error: tool 'xcodebuild' requires Xcode错误解决方法

From: http://blog.csdn.net/jymn_chen/article/details/21613745 因为机子里有两个Xcode&#xff0c;所以分别重命名了&#xff0c;但是在运行一个MakeFile时却报了以下错误&#xff1a; [plain] view plaincopyxcodebuild -target "GHUnitIOS (Device)" -configu…

利用Bdrive打造个人私有云存储解决方案

Bdrive 一款私有云储存软件&#xff0c;可以自己方便的在 Mac/Windows 下架设服务器&#xff0c;并可以通过 PC、Mac、iOS、Android 跨平台使用。以下简单介绍一下利用Bdrive来完成个人私有云存储解决方案。 第一步&#xff0c;搭建Bdrive云存储服务器 先下载Bdrive服务器程序&…

MVC路由中routes.IgnoreRoute({resource}.axd/{*pathInfo}) 到底什么意思!

转自&#xff1a;http://blog.csdn.net/lvjin110/article/details/24638913 参考&#xff08;1&#xff09; http://www.cnblogs.com/flyfish2012/archive/2013/02/01/2889184.html 我们在开发MVC当中&#xff0c;经常在我们的全局类的路由设置&#xff0c;看到这样的代码&…

未能加载文件或程序集“Autofac, Version=3.4.0.0,

遇到这个错误的时候&#xff1a;如下图 未能加载文件或程序集“Autofac, Version3.4.0.0, Cultureneutral, PublicKeyToken17863af14b0044da”或它的某一个依赖项。找到的程序集清单定义与程序集引用不匹配。 只要在config加上 <runtime><assemblyBinding xmlns"…

浅谈关于SRAM与DRAM的区别

从名字上看&#xff0c;SRAM与DRAM的区别只在于一个是静态一个是动态。由于SRAM不需要刷新电路就能够保存数据&#xff0c;所以具有静止存取数据的作用。而DRAM则需要不停地刷新电路&#xff0c;否则内部的数据将会消失。而且不停刷新电路的功耗是很高的&#xff0c;在我们的PC…

字符串系列之最长回文子串

2019独角兽企业重金招聘Python工程师标准>>> 问题描述&#xff1a; 给定一个字符串SA1A2...An&#xff0c;要求找出其最长回文子串&#xff08;Longest Palindromic Substring&#xff09;。所谓回文子串就是S的某个子串Ai...Aj为回文。例如&#xff0c;对字符串Sab…