gcc gdb
来源:互联网 发布:nginx 不同域名跳转 编辑:程序博客网 时间:2024/06/05 02:13
gcc 最初是 "GNU C Compiler" 的简称,只是当作一个 C 语言的编译器,现在已经变成了 "GNU Compiler Collection",可以编译多种语言。
二、编译的四个阶段:
在使用 gcc 编译程序时,编译过程可以被细分为 4 个阶段:
◆ 预处理(Pre-Processing)
◆ 编译(Compiling)
◆ 汇编(Assembling)
◆ 链接(Linking)
下面以一份简单的 C 代码来说明:
#include <stdio.h>int main(){ printf("Hello,World!\n");}
在使用GCC编译时, 可以直接使用 gcc test.c -o test 命令一次性完成编译(注:如果使用 gcc 命令(未做g++的软链接)来编译 C++ 库,需要链接 -lstdc++ 库。),也可以分成 4 个步骤分步编译:
gcc -E test.c -o test.i //需要使用 -o 将预处理后的结果输出到 test.i (仍然是 C 代码),否则只将预处理后结果输出到屏幕。gcc -S test.i //将预处理后的代码进行反汇编,生成汇编代码gcc -c test.s //将汇编代码编译成目标文件,即二进制代码。-c 可以直接把 C/C++ 代码编译成机器代码。gcc test.o -o test //将中间文件链接成可执行文件
本人使用的 gcc 安装在 CentOS 6.4 64bit 上的 /usr/local/gcc-4.8.1 目录,故以此为例说明:
1、预处理阶段:调用 cpp 命令
cpp test.c -o test.i
2、编译阶段:调用 cc1 命令(如果是 C++ 代码则使用 cc1plus 命令)
/usr/local/gcc-4.8.1/libexec/gcc/x86_64-unknown-linux-gnu/4.8.1/cc1 test.i
3、汇编阶段:调用 as 命令
as test.s -o test.o
4、连接阶段:调用 ld 命令(如果是 C++ 代码还需要链接 -lstdc++)
ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o test test.o /usr/lib64/crt1.o /usr/lib64/crti.o -lc /usr/local/gcc-4.8.1/lib/gcc/x86_64-unknown-linux-gnu/4.8.1/crtbegin.o /usr/local/gcc-4.8.1/lib/gcc/x86_64-unknown-linux-gnu/4.8.1/crtend.o /usr/lib64/crtn.o
注:可以通过 -v 参数打印出编译器内部编译各过程的命令行信息和编译器的版本,如 gcc -v test.c 2>&1 | grep cc1 ,另外关于 ld 链接 C/C++ ,可参考:
http://stackoverflow.com/questions/14163208/how-to-link-c-object-files-with-ld
三、头文件的处理
GCC 的 -I 参数可以用来指定头文件目录,当前目录和缺省目录(/usr/include && /usr/local/include)不用指定。
另外,-include 用来包含某个头文件,但一般情况下因为已经在源码里用 #include 指令实现了,所以这个很少用。
环境变量 C_INCLUDE_PATH 和 CPLUS_INCLUDE_PATH ,分别用来指定 C 与 C++ 的默认的头文件目录。那么,该方法与使用 -I 来包含头文件有什么区别呢?区别是,在使用 GCC -MM 生成依赖关系时,前者会忽略生成指定路径的头文件的依赖关系,即编译时不依赖于环境变量中指定的头文件,这在我们使用稳定的第三方库头文件时,非常有利于编译检查的时间优化,如 BOOST 库等。-M 选项生成文件关联的信息,包含目标文件所依赖的所有源;而 -MM 选项会忽略由 #include <...> 造成的依赖关系。-MD 选项与 -M 相同,只是将依赖关系写到 .d 文件中去,同样的 -MMD 也是如此。
在代码中使用 #include <stdio.h> 与使用 #include "stdio.h" 一般都可以使代码正常编译,但对于预处理程序 cpp 的工作效率是有影响的,前者会优先搜索系统默认头文件目录,而后者会优先搜索当前目录,如果没有搜到就到 -I 指定的目录去搜索。
四、链接库的处理
下面先来看看静态库和动态库是如何生成的:
◆ 静态链接库其实就可以看成是 *.o 文件的简单打包:
ar -cr libtest.a test.o 或: ar -cr libtest.a test.cpp
-r 选项是必须的,表示插入到备份文件,并且有则替换 -c 表示创建备份文件
(一段小历史:最早的 ar 命令只是用来打包文件的,就类似 tar 一样,并不处理 .o 文件里的符号表,所以出现一个名为 ranlib 的工具来做这件事,后来被厂商集成到 ar 命令中,相当于 ar -s,现在只要使用 ar 命令,已经默认使用了 ranlib 的功能。)
◆ 动态链接库优于静态链接库,无论是在内存使用和磁盘使用上(多进程时),既可以使用源码文件直接生成,也可以使用 *.o 文件生成(*.o 文件的生成必须使用 -fPIC 参数,而从*.o 文件生成动态链接库则不用再重复此参数):
gcc -c -fPIC test.cpp && gcc test.o -o libtest.so -shared或: gcc test.cpp -o libtest.so -shared -fPIC
-shared 该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件。
-fPIC 表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的,所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。
(小知识:可以通过 ldd 命令来查看可执行文件需要哪些动态链接库)
再来看看 gcc 如何使用静态库和动态库:
编译时可以直接带上完整路径的链接库进行编译,如 g++ main.cpp lib/libmyfun.so -o main ,但更多的是使用下面的方法。
gcc 使用 -l 参数来指定链接库,如链接 libtest.a 或 libtest.so,使用 -ltest。使用 -l 链接时去除 lib 前缀和后缀即可。
编译时,默认的链接库目录为 /usr/lib && /usr/local/lib ,如果是64位系统,还有 /usr/lib64;否则就需要通过 -L 参数来指定自定义链接库目录。(注:有的LINUX发行版好像不支持 /usr/local/lib 和 /usr/local/lib64,所以最好是使用 /usr/lib)
静态库链接后,是将代码直接嵌入到可执行文件中,所以运行时,不需要再指定静态库;而动态库则不然。如果动态库位于系统默认的库目录,则可直接运行,如果是自定义目录,有以下三种方法可以配置:
1、直接执行:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/project/lib
可临时生效,(重启后失效)
2、 ~/.bashrc 或者 ~/.bash_profile 里添加上面的命令:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/project/lib
重启终端生效。
3、编辑 /etc/ld.so.conf 文件(或者在 /etc/ld.so.conf.d 目录下新建一个 project.conf 文件),添加自定义动态库路径,然后使用 ldconfig 命令更新,使其生效。
当静态库和动态库同时存在时,程序会优先使用动态库,如果需要强制只使用静态库,可使用 -static 选项,此时不会链接任何动态库,生成的目标文件会比较大。
此外,还有另外一种方式,在链接时加上如 -L../lib -Wl,-rpath=../lib 选项,则可以指定编译好的程序在运行时动态库的优先搜索目录,这种方法会将动态库路径写入到elf文件中去,即使找不到,也可以再去环境变量指定路径去寻找。该方式便于部署,可把动态库跟配置文件一样放在单独文件夹里,与可执行文件一起打包发给运维即可运行。总结一下:-rpath制定的搜索路径,优先于LD_LIBRARY_PATH 和 /etc/ld.so.conf ,另外它是被写入到可执行文件中的。可以使用 ldd 命令查看可执行文件中使用的动态链接库和相应路径。
(小知识:我们可以给链接库带一个版本号,如 libmyfun.so.6,然后用 ln -s libmyfun.so.6 libmyfun.so,项目中链接 libmyfun.so 即可。gcc库文件的 libstdc++.so.6 就是如此)
运行时,Linux动态链接库的搜索路径按优先级排序为:
1、编译目标代码时 ”-Wl,-rpath,” 指定的动态库搜索路径(当指定多个动态库搜索路径时,路径之间用冒号”:”分隔。);
2、环境变量 LD_LIBRARY_PATH 指定的动态库搜索路径;
3、配置文件 /etc/ld.so.conf 中指定的动态库搜索路径;
4、默认的动态库搜索路径 /lib,如果是64位系统还包括 /lib64;
5、默认的动态库搜索路径 /usr/lib,如果是64位系统还包括 /usr/lib64;
注意: /usr/local/lib 和 /usr/local/lib64 居然不在标准路径之列。
五、静态检查
-pedantic //检查代码是否符合 ANSI/ISO 标准:-Wall //输出警告信息-Wextra //输出更多的警告信息-Weffc++ //检查是否符合 《Effective C++》这本书中提到的标准-w //关闭所有警告
六、调试与优化
-g //在编译时生成原生格式的调试符号信息,可以使用 gdb 或 ddx 等调试器调试。-g 分为三个级别,默认为 -g2,其中 -g3 除包含 -g2 中的所有调试信息外,还包含源代码中定义的宏。-ggdb //在编译时生成 gdb 专用格式的调试符号信息,信息更为丰富,但只能使用 gdb 调试,而不能使用其它调试器。级别设定与 -g 基本相同。-gdwarf //如果升级 GCC 之后,发现 GDB 不能用了,那么在编译的时候加上 -gdwarf 选项试一下,如果还不行,再使用 -gdwarf-2 试试。-p //将剖析(Profiling)信息加入到最终生成的二进制代码中,生成可以被通用剖析工具(Prof)能够识别的统计信息,它对于找出程序瓶颈很有帮助。生成后的可执行文件要运行一次,才能生成剖析文件,默认文件名是 gmon.out。-pg //生成只有 GNU 剖析工具(Gprof)才能识别的统计信息。-save-temps //保存编译过程中生成的一系列中间文件。-On //n 可以为 0~3,默认为 1,数字越大优化越高,一般使用 -O2 可以在优化长度、编译时间和代码大小之间取得较好平衡,开发调试建议使用 -O0-Os //相当于 -O2.5,使用了所有 -O2 的优化选项,但却没有使用增加大小。也就是说,相对于 -O2,能减少一点可执行文件的大小。-s //从执行文件中删除符号表与重定向信息,以减少执行文件的大小。跟直接编译之后,使用 strip 命令减小文件大小效果一致。
七、编译器扩展
#include <iostream>#include <string>class T{ public: void fun(std::string s,double d) { std::cout<<__FUNCTION__<<std::endl; //输出函数名 std::cout<<__PRETTY_FUNCTION__<<std::endl; //输出函数完整原型 }};int main(){ T().fun("Hello",3.1);}
输出结果如下:
八、GCC 版本升级
通过源码安装新版本的GCC,要保证已经安装了GCC编译器:
1、下载源码包
下载高版本GCC源码包
ftp://ftp.gnu.org/gnu/gmp 下载最新版
http://ftp.gnu.org/gnu/mpfr/ 下载 mpfr 最新版
http://www.multiprecision.org/mpc 下载 mpc 最新版
2、依次安装 gmp 、mpfr、mpc
./configure --prefix=/usr/local/gmp-5.1.1&& make -j4 && make install./configure --prefix=/usr/local/mpfr-3.1.2 --with-gmp=/usr/local/gmp-5.1.1&& make -j4 && make install./configure --prefix=/usr/local/mpc-1.0 --with-gmp=/usr/local/gmp-5.1.1 --with-mpfr=/usr/local/mpfr-3.1.2&& make -j4 && make install
3、安装GCC
//export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/mpc-1.0/lib:/usr/local/gmp-5.1.1/lib:/usr/local/mpfr-3.1.2/lib./configure --prefix=/usr/local/gcc-4.8 --enable-threads=posix --disable-checking --disable-multilib --enable-languages=c,c++ --with-gmp=/usr/local/gmp-5.1.1 --with-mpfr=/usr/local/mpfr-3.1.2 --with-mpc=/usr/local/mpc-1.0 --with-gmp-lib=/usr/local/gmp-5.1.1/lib --with-mpfr-lib=/usr/local/mpfr-3.1.2/lib --with-mpc-lib=/usr/local/mpc-1.0/libmake -j4 && make install
4、做软链接
ln -s /usr/local/gcc-4.8/bin/gcc /usr/bin/gccln -s /usr/local/gcc-4.8/bin/g++ /usr/bin/g++cp /usr/local/mpc-1.0/lib/libmpc.so /usr/local/gmp-5.1.1/lib/libgmp.so /usr/local/mpfr-3.1.2/lib/libmpfr.so /lib64/
这时候用gcc来编译C++程序会报缺少一些C++标准库,这时候可以使用 gcc -lstdc++ 或直接使用 g++ 命令。
5、解决 GDB 的问题
升级 gcc 之后,编译代码加上 -g 选项,在使用 gdb 调试时,可能会出现 Missing separate debuginfos, use: debuginfo-install libgcc-4.4.7-3.el6.x86_64 这样的错误,是因为只加 -g 选项,此时已经无法生成符号表了。解决方案有两种:
◆ 编译时加上 -gdwarf 选项试一下,如果还不行,再使用 -gdwarf-2 试试。
◆ 升级 gdb 版本,可升级至 7.6 ,正常的 ./configure && make && make install 之后,就可以正常使用了。如果 make 过程中出现 configure: error: no termcap library found 的错误,可以 yum install ncurses-devel 来解决。
预编译头:
gcc -x c++-header -c stdc++.h -o stdc++.h.gch -std=c++0x
6、当执行一个可执行文件时,如果报类似 `GLIBCXX_3.4.15' not found 的错误,是因为编译这个可执行文件的GCC版本和当前环境中的GCC版本不同,可以选择升级GCC版本,如下网页:
http://blog.csdn.net/davidwang9527/article/details/19197511、
7、如果只是将GCC4.8.2编译的可执行文件配置到一台LINUX机器上运行,并不需要安装GCC4.8.2,只需要将原机器上的 /usr/local/gcc-4.8.2/lib64/libstdc++.so.6 文件复制到新机器的 /usr/lib64/libstdc++.so.6 即可(注意备份原文件)
一、简介:
GDB 是 GNU Debugger 的简称,是 GNU 软件系统中的标准调试器。
二、启动 GDB
gdb 启动时,需要载入符号表文件(symbol table file)、可执行文件(executable file)、源代码文件。
使用 gcc 编译程序时,使用 -g\-ggdb\-gdwarf-2 参数,生成的可执行文件内置了 gdb 调试用的符号表信息和源代码路径信息。启动 GDB 的方式有以下几种:
1、gdb <program>
默认使用 <program> 中记录的符号表信息和源代码路径信息,注意,如果源代码路径变化了,需要使用 -directory/-d 来重新指定源代码路径,否则 gdb 调试时无法 list ,此外该命令实际相当于 gdb -se <program> ,也可以使用 -s 单独指定符号表文件,或使用 -e 单独指定可执行文件。
2、gdb -c <core dump> <program>
使用 core dump 文件来调试不易复现的问题。
3、gdb <program> --pid=<pid>
调试服务程序时,可以指定这个程序运行时的进程ID,gdb 会自动 attach 上去并调试它。
还有一种方法:
先 gdb <program> 运行起来,然后通过 attach PID 来关联上进程即可,并用 detach 来取消挂接的进程。
启动带命令行参数的程序:
-agrs 可以在启动时使用此参数,指定程序运行的命令行参数,如 gdb -args <program> [options],也可以启动 gdb 后,使用 set args [options],或使用 r [options] 带上命令行参数直接运行。
三、查看命令
info registers
info all-registers
info registers <regname,...> //p $eax ,使用 print 的命令输出寄存器中的值,需要使用 $ 前缀。
backtrace/bt 查看栈信息,还可以 bt <num> num 可正可负,表示栈顶或栈底的 num 层信息。
frame/f <num> 查看从栈顶(栈顶计为第0层)开始的第 n 层栈的信息。
info args 打印出当前函数的参数的值
info locals 打印出当前函数中所有的局部变量的值
info catch 打印出当前的函数中的异常处理信息
查看源代码:
list +/-/<num>/<first,last>/<,last>/<filename:linenum>/<function>/<filename:function>/<class::function>/<* address>
set/show listsize <count>
查看源代码中的内存:
info line <num>/<function>
disassemble <function>
修改变量在运行时的值:
p var=value ,或 set var=value
whatis var 可查看变量的类型
ptype:比whatis的功能更强,他可以提供一个结构的定义
表达式:
@ 和数组有关的操作符,左边是数组首地址的值,右边是想查看的长度。如果是静态数组,直接 p arrayname 就可以查看。
:: 指定一个在文件或是一个函数中的变量
{<type>}<addr> 表示一个指向内存地址<addr> 的类型为 <type> 的一个对象
int *array = (int*) malloc(len*sizeof(int));
p *array@len
变量的自动显示:
display <expr>
display/i $pc /i 表示输出格式为机器指令码即汇编,$pc 是 gdb 的环境变量,表示指令的地址。当程序停下来时,就会出现源代码和机器指令码相对应的情形。
undisplay <dnums> 取消自动显示,<dnums> 表示 display 的编号,可以通过 display 命令查看。dnums 可以是一个值,也可以是多个用空格分开的值,也可以是范围值,使用 - 连接。
delete display <dnums> 同上
disable/enable display <dnums> 临时关闭/打开某些变量的自动显示
GDB中有几种让程序暂停的方式:断点(BreakPoint)、观察点(WatchPoint)、捕捉点(CatchPoint)、信号(Signals)、线程停止(ThreadStops)
break <arguments> ... if <condition>
观察点一般用来观察某个变量或表达式的值是否发生变化,如果有变化,马上停住程序,而且也像 break 一样支持 if 条件。
watch <expr>
rwatch <expr> 当变量被读时,停住程序
awatch <expr> 当变量被读或被写时,停住程序
info watchpoints 列出所有观察点
捕捉点一般用来捕捉程序运行时的一些事件,如载入动态链接库或是C++异常。
catch <event> 当 <event> 发生时,停住程序,event 可以是 throw\catch\exec\fork\vfork\load\load <libname>\unload\unload <libname>
tcatch <event> 只设置一次捕捉点,当程序停住以后,该点被自动删除。
断点维护:
clear 清除所有断点
clear <function>/<filename:function>/<classname::function> 清除所有 function 上的断点
clear <linenum>/<filename:linenum> 清除指定行上的断点
delele breakpoint [num...] 按断点编号删除断点,如果不写 [num...],则删除所有断点
disable/enable b [num...] 临时关闭/打开断点
条件断点的维护:
condition <bnum> <expr> 修改断点的停止条件,如果没有 <expr>,则表示清除断点的停止条件
ignore <bnum> <count> 忽略停止条件几次
断点的自动化操作:
commands [bnum] command-list end
如:
break funif x>0
commands
printf "x is %d\n",x
continue
end
如果要取消命令序列,只需要简单的执行一下 command 命令之后打个 end 即可。
单步调试:
step <count>
next <count>
continue/c/fg <ignore-count>
finish 跳出函数
until/u [num] 运行到哪一行,不使用 [num] 可以用来退出循环。
stepi/si/nexti/ni 相当于 display/i $pc ,单步跟踪一条机器指令,即汇编代码。
信号调试:
handle <signal> <keywords ...>
keywords 可以为以下的关键字:
nostop 收到信号时,不停止程序,但会打印出消息,通知收到这种信号
stop 停止程序
print 显示出信息
noprint 不显示信息
pass/noignore 不处理信号,但会把信号交给调试程序处理
nopass/ignore 处理信号,不会让调试程序处理
可以通过 info signal /info handle 查看有哪些信号在被 gdb 检测中。
signal <signal> gdb提供的 signal 命令,可以产生一个信号量在被调试的程序,就可以在程序全速运行时在任意位置设置断点,并在该断点上用 gdb 产生一个信号量,可以精确的在某处产生信号。
signal 命令和 shell的 kill 命令不同,前者是 gdb 发出给调试程序的,后者是 kill 命令发信息给调试程序,由 gdb 先截获的。
多线程调试:
break <line> thread <threadno> [if ...]
注意,此处的线程ID是由 gdb 分配的,可以通过 info threads 来查看正在运行的程序的线程信息,如果不指定线程ID,则表示断点设在所有线程上面。
跳转控制命令:
jump <line>/<address> 修改程序的执行顺序,可以让程序执行随意跳跃。
return <expr> 强制未执行完的函数返回,如果指定了 <expr> ,则其为指定的返回值。
call <expr> 强制调用函数,并显示函数返回值,如果是 void ,则不显示。
调试智能指针,如果智能指针是user_ptr,则:
p user_ptr //输出形如:$27 = {px = 0x826b370, pn = {pi_ = 0x826a008}}
p user_ptr.px->userid //查看智能智能指针指向对象中的成员值
p *(user_ptr.pn.pi_) //查看智能指针引用记数
使用GDB调试数组和vector容器:
int array[4] = {1,2,3,4}
p array[0]@sizeof(array)/sizeof(array[0])
p *(array+1)
p *(vect._M_impl._M_start)@vect.size()
p *(vect._M_impl._M_start + 3) //查看第四个元素的值
vector的源码,vector中只有一个成员变量_M_impl。这个成员变量是一个结构体,结构体又由三个类型指针构成:
struct //伪代码
{
T* _M_start;
T* _M_finish;
T* _M_end_of_storage;
}_M_impl;
_M_start指向vector的开头,相当于v.begin()所返回的位置;
_M_finish指向的是最后一个元素的结束位置之后,相当于v.end()所返回的位置。
_M_end_of_storage指向所分配空间的末尾,相当于v.begin() + v.capacity()*sizeof(T)
启动gdb的时候自动执行脚本
gdb -x gdb_script
gdb_script里面只能有gdb命令,但是 gdb命令里面有个 shell 指令,所以实际上这里可以做任何事。
查看反汇编代码
disassemble fun 查看 fun 函数的反汇编代码
disassemble /r 0x401365,0x401370 查看一段内存地址
info line fun 查看 fun 函数的起始地址
gdb test -tui
加上 -tui 参数后,就可以边调试边看代码了,实用。
机器语言工具
有一组专用的gdb变量可以用来检查和修改计算机的通用寄存器,gdb提供了目前每一台计算机中实际使用的4个寄存器的标准名字:
$pc : 程序计数器
$fp : 帧指针(当前堆栈帧)
$sp : 栈指针
$ps : 处理器状态
up 上移栈帧,使另一函数成为当前函数
down 下移栈帧,使得另一个函数成为当前函数
make 不退出 gdb 就可以重新编译程序
显示设置:
set print address [on/off] 打开/关闭地址输出,当程序显示函数信息时,gdb 会显出函数的参数地址,默认为 on。show print address
set print array [on/off] 打开数组显示,打开后每个元素占一行,如果不打开,则线个元素以逗号分隔,默认为 off。 show print array
set print elements <number-of-elements> 设置数据显示的最大长度,如果设定为0,表示不限制。show print elements
set print null-stop [on/off] 打开此选项时,当显示字符串时,遇到结束符就停止显示,默认为 off。
set print pretty [on/off] 打开此选项时,当 gdb 显示结构体时会比较漂亮,会分行显示,否则显示在一行。show print pretty
set print sevenbit-strings [on/off] 设置字符显示是否按 \nnn 的格式显示,如果打开,则字符串或字符数据按 \nnn 显示,如 \065。show print sevenbit-strings
set print object [on/off] C++中,如果一个对象指针指向其派生类,打开此选项时,gdb 自动按照虚方法调用的规则显示输出,否则 gdb 不管虚函数表。默认是 off。show print object
set print static-members [on/off] 显示一个 C++ 对象时,是否显示其中的静态数据成员,默认为 on。show print static-members
set print vtbl [on/off] 当打开时,gdb 将使用比较规整的格式来显示虚函数表示,默认是关闭的。show print vtbl
- gcc gdb
- gcc gdb
- gcc gdb
- GDB 调试常用命令 GCC
- GCC参数详解gdb
- GCC参数详解gdb
- gcc gdb的使用
- 初识vim+gcc+gdb
- gcc and gdb
- GCC与GDB
- Emacs+GCC+GDB环境
- gcc gdb 学习
- VIM、GCC和GDB
- gcc gdb 整理
- GCC+VIM+GDB
- gdb 调试 GCC 程序
- gcc和gdb
- gcc/makefile/gdb常用命令
- iOSNSArray枚举
- VS2010的快捷键;VS2012变化的快捷键
- db文件 导出 Failed to pull selection问题
- 第二题
- 第一次跳槽感悟
- gcc gdb
- 【POJ】1002 - 487-3279
- IOS-Foundation-内存管理
- 一些碰到的ora错误
- zxing实现二维码生成和解析
- 第三题
- java引用传递
- 浙江省赛
- uva 10635 Prince and Princess(DP)