引言  - 从"HelloWorld"开始

  Makefile 是Linux C 程序开发最重要的基本功. 代表着整个项目编译和最终生成过程.本文重点是带大家了解真实项目中那些简易的Makefile规则构建.

本文参照资料

   GNU make   -  https://www.gnu.org/software/make/manual/make.html  

   跟我一起写Makefile  - http://wiki.ubuntu.org.cn/%E8%B7%9F%E6%88%91%E4%B8%80%E8%B5%B7%E5%86%99Makefile:%E6%A6%82%E8%BF%B0 

   入门基础Makefile概述  - https://github.com/loverszhaokai/GNUMakeManual_CN

推荐需要简单看看上面资料. 特别是第三个入门教程, 了解基础make语法.  看完后那我们扩展之路开始了, 先hello world 讲起. 素材 mian.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h> #define ALEN(arr) (sizeof(arr)/sizeof(*arr)) /*
* 简单的demo测试
*/
int main(int argc, char * argv[]) {
int i;
const char * strs[] = {
"走着走着,就散了,回忆都淡了",
"看着看着,就累了,星光也暗了;",
"听着听着,就醒了,开始埋怨了;",
"回头发现,你不见了,突然我乱了。",
}; srand((unsigned)time(NULL));
for(;;) {
/*
* \e[ 或 \033[ 是 CSI,用来操作屏幕的。
* \e[K 表示从光标当前位置起删除到 EOL (行尾)
* \e[NX 表示将光标往X方向移动N,X = A(上) / B(下) / C(左) / D(右),\e[1A 就是把光标向上移动1行
*/
printf("\033[1A\033[K"); //先回到上一行, 然后清除这一行 // 随机输出一段话
i = rand()%ALEN(strs);
puts(strs[i]); sleep();
} return ;
}

编译上面程序的第一个Makefile 文件内容如下

main.out:main.c
gcc -g -Wall -o $@ $^

执行过程就是通过shell执行make, 我们简单翻译一下上面写法的含义.

  目标 main.out  依赖 main.c ;  main.c 已经存在(因为是存在的文件) 那就执行规则 (gcc -g -Wall -o $@ $^).

  其中 $@ 表示所有目标, $^表示所有依赖. 

是不是很简单.当然上面Makefile还存在一些潜规则.

  所有执行规则都是以\t开始; 第一个目标就是make过程唯一执行的起点;

再讲之前我们再扯一点gcc 相关的积累知识. 否则写Makefile都是无米之炊.

# 中间插入一段关于gcc 的前戏
gcc –E –o main.i mian.c    # -E是预处理展开宏,生成详细c文件, -o是输出
gcc –S –o main.s main.i    # -S 是编译阶段, 将c文件生成汇编语言
gcc –c –o main.o main.s    # -c 是汇编阶段, 生成机器码
gcc –o main.exe main.o     # 链接阶段, -o 生成目标执行程序 gcc –g      # 编译中加入调试信息, 方便gdb调试, 还有-ggdb3 支持宏调试等
gcc –Wall    # 输出所有警告信息
gcc –O2 # 开启调优, O2等级调优 gcc –I(i大写) # 导入头文件目录,方便 *.h文件查找
gcc –L(l 大写)   # 导入库文件目录,方便 *.so和*.a文件查找
gcc –l(l 小写) # 导入库文件, 例如-lpthread, 相当于依次查找导入 libpthread.so/libpthread.a 文件
gcc –static –l(l 小写) # 指定只查找 静态库 lib*.a 文件, linux约定库文件都是 lib开头 ar rc libheoo.a hello.o world.o # 将*.o 文件打包成 libheoo.a 静态库
gcc –fPIC –shared –o libheoo.so hello.o world.o # 将*.o 文件打包成 libheoo.so 动态库

到这里储备方面的讲完毕了.   --<-<-<@

前言  -  介绍一下实际例子中语法套路

  首先升级一下上面Makefile文件, 如下(如果你复制没法执行, 请检查规则开头字符是\t)

# 构建全局编译操作宏
CC = gcc
CFLAGS = -g -Wall -O2
RUNE = $(CC) $(CFLAGS) -o $@ $^
RUNO = $(CC) -o $@ $< # 构建伪命令
.PHONY:all clean cleanall # 第一个标签, 是make的开始
all:main.out main.out:main.c
$(RUNE) # 清除操作
clean:
-rm -rf *.i *.s *.o *~
cleanall:clean
   -rm -rf *.out *.out *.a *.so

我们先说一下Makefile中变量的使用, 就是上面 "="那种基础语法说明.

关于Makefile 变量总结如下

关于上面变量的使用这里做一个总结. 

a. = 声明变量
加入存在下面场景

CC = cc

CC = gcc 那么make的时候, $(CC) 就是 gcc, 会全局替换.
对于 = 声明的可以认为是一个全局递归替换宏. b. := 声明变量 …
srcdir := ./coroutine
tardir := ./Debug

上面就是一般语言中普通变量. c. ?= 声明变量 Foo ?= bar 上面意思是 $(foo)不存在, 那就将 bar 给它. 等同于
ifeq ($(origin FOO), undefined)
FOO = bar
endif d. += 声明变量 objects = main.o foo.o bar.o utils.o
objects += another.o
等同于 objects = main.o foo.o bar.o utils.o
objects := $(objects) another.o

趁着热度举个例子, 先不解惑.

CC = cc
FOO := foo
BAR ?= bar
HEO := heo all :
echo $(CC)
echo $(FOO)
echo $(BAR)
echo $(HEO) HEO += world
FOO := FOO
CC = gcc

执行结果如下, 如下图 . 通过Demo外加上下面运行结果图, 应该会有收获.

通过上面我们可以发现 := 和 = 声明的变量都是最终全局替换之后的结果. 他们二者细微差别, 我还是通过例子来说吧.

一切都在不言中, 那么关于Makefile变量中语法讲解完毕. 顺带说一些小细节吧,

  1). Makefile 中 一切从简单开始, 能用 = 就不要用 :=

  2). 变量具备全部作用域 , 推荐全部用大写命名

  3). 多查最开始我推荐的资料

接着变量往后讲,继续分析其它例子

上面 .PHONY 是 Makefile中伪命令. 默认套路写法. 定义命令名称, 可以通过 make 命令名称调用.

其中 all 是Makefile第一个运行目标,  从它入口. clean , cleanall 伪命令 通过 make clean ; make cleanall 执行.

主要是清除生成的中间文件. 希望你能明白, 自己演示一下, 是不是这样的.

这里我们开始一个新的例子了. 具体参照

  C协程库的编译文件  https://github.com/wangzhione/scoroutine/blob/master/Makefile

# 全局替换变量
CC = gcc
CFLAGS = -g -Wall -O2
RUNE = $(CC) $(CFLAGS) -o $@ $^ # 声明路径变量
SRC_PATH := ./coroutine
TAR_PATH := ./Debug # 构建伪命令
.PHONY:all clean cleanall # 第一个标签, 是make的开始
all:$(TAR_PATH)/main.out $(TAR_PATH)/main.out:main.o scoroutine.o
$(CC) $(CFLAGS) -o $@ $(addprefix $(TAR_PATH)/, $^ ) $(TAR_PATH):
mkdir $@ %.o:$(SRC_PATH)/%.c | $(TAR_PATH)
$(CC) $(CFLAGS) -c -o $(TAR_PATH)/$@ $< # 清除操作
clean:
  -rm -rf $(TAR_PATH)/*.i $(TAR_PATH)/*.s $(TAR_PATH)/*.o $(TAR_PATH)/*~
cleanall:clean
  -rm -rf $(TAR_PATH)

从头开始分析它的具体含义.

1) 开头全局变量定义部分, 个人习惯问题其实也可以用 := . 最终得到 RUNE = gcc -g -Wall -O2 -o $@ $^ .

2) 路径声明部分, 用 := 声明, 支持中间拼接. 用=也可以, 都是条条大路同罗马, 自己多检查一下. 以后我可能全部用 = 声明全局递归的字面变量声明.

3) .PHONY 声明了 3个伪命令. 不会立即执行的命令, 依赖 make 命令名称 主动调用

4) all 依赖 于 $(TAR_PATH)/main.out 就是依赖于 ./coroutine/main.out. 刚好下面存在

$(TAR_PATH)/main.out:main.o scoroutine.o
$(CC) $(CFLAGS) -o $@ $(addprefix $(TAR_PATH)/, $^ )

这条规则. 其中又依赖于 main.o 和 scoroutine.o 目标. 那么二者也会做新的目标, 就这样递归的找下去.
后面找到了 %.o, Makefile中%是匹配符, 例如 main.o % 就相当于 main部分.
其中addprefix 是GNU make内置的函数的其中一个, 需要用到的时候多查文档就行了.

为每一个可以分割的子单元上加上一个前缀, 这个前缀就是它的第一个参数.

5) 对于下面这段很实用, 通配符 + | 生成必要文件的语法

%.o:$(SRC_PATH)/%.c | $(TAR_PATH)
$(CC) $(CFLAGS) -c -o $(TAR_PATH)/$@ $<

以上是一个通用匹配规则, %.o 目标依赖于 ..../%.c 具体文件. 后面 | 跟的也是一个依赖目标. 这个目标只会在第一次不存在的时候才会被构建.

更加详细的说明可以参照第一个参照资料 "4.3 Types of Prerequisites" 部分.  这个语法用的很多, 用于构建一次生成所需的目录信息.

6) 最后就是剩余clean, cleanall伪命令. 定义清除中间文件等.

是不是想骂die, 但是上面那些都自行捣鼓了一遍, 基本就越过Makefile初级部分, 能够写出能看的编译文件O(∩_∩)O哈哈~

正文  - 来个小框架Makefile试试水

  先找一个特别老的, 很水的一个Makefile 试试. 具体参照

  一个控制台小项目编译文件  https://github.com/wangzhione/sconsole_project/blob/master/linux_sc_template/Makefile

C = gcc
DEBUG = -g -Wall -D_DEBUG
#指定pthread线程库
LIB = -lpthread -lm
#指定一些目录
DIR = -I./module/schead/include -I./module/struct/include
#具体运行函数
RUN = $(CC) $(DEBUG) -o $@ $^ $(LIB) $(DIR)
RUNO = $(CC) $(DEBUG) -c -o $@ $^ $(DIR) # 主要生成的产品
all:test_cjson_write.out test_csjon.out test_csv.out test_json_read.out test_log.out\
test_scconf.out test_tstring.out #挨个生产的产品
test_cjson_write.out:test_cjson_write.o schead.o sclog.o tstring.o cjson.o
$(RUN)
test_csjon.out:test_csjon.o schead.o sclog.o tstring.o cjson.o
$(RUN)
test_csv.out:test_csv.o schead.o sclog.o sccsv.o tstring.o
$(RUN)
test_json_read.out:test_json_read.o schead.o sclog.o sccsv.o tstring.o cjson.o
$(RUN)
test_log.out:test_log.o schead.o sclog.o
$(RUN)
test_scconf.out:test_scconf.o schead.o scconf.o tree.o tstring.o sclog.o
$(RUN)
test_tstring.out:test_tstring.o tstring.o sclog.o schead.o
$(RUN) #产品主要的待链接文件
test_cjson_write.o:./main/test_cjson_write.c
$(RUNO)
test_csjon.o:./main/test_csjon.c
$(RUNO)
test_csv.o:./main/test_csv.c
$(RUNO)
test_json_read.o:./main/test_json_read.c
$(RUNO)
test_log.o:./main/test_log.c
$(RUNO) -std=c99
test_scconf.o:./main/test_scconf.c
$(RUNO)
test_tstring.o:./main/test_tstring.c
$(RUNO) #工具集机械码,待别人链接
schead.o:./module/schead/schead.c
$(RUNO)
sclog.o:./module/schead/sclog.c
$(RUNO)
sccsv.o:./module/schead/sccsv.c
$(RUNO)
tstring.o:./module/struct/tstring.c
$(RUNO)
cjson.o:./module/schead/cjson.c
$(RUNO)
scconf.o:./module/schead/scconf.c
$(RUNO)
tree.o:./module/struct/tree.c
$(RUNO) #删除命令
clean:
rm -rf *.i *.s *.o *.out __* log ; ls -hl
.PHONY:clean

上面那些注释已经表达了一切了吧, 确实好水. 但是特别适合练手, 每一个生成目标都有规则对应. 费力但是最直接. 实在没有没有好讲的, 扯一点

1) GNU make 指定的编译文件是 makefile 或 Makefile. 推荐用Makefile, 是一个传统吧. 因为C项目都是小写, 用大写开头以作区分.

2) Makefile 中 同样以 \ 来起到一整行的效果

3) 其它目标, 依赖, 规则.只要存在那么Makefile就可以自动推导. 当然它依赖文件创建时间戳, 只有它变化了Makefile才会重新生成目标.

Makefile点心结束了. 以上就是make使用本质, 生成什么, 需要什么, 执行什么. 推荐练练手, 手冷写不了代码.

最后来点水果

  simplec c的简易级别框架  https://github.com/wangzhione/simplec/blob/master/Makefile

##################################################################################################
# .前期编译辅助参数支持 #
##################################################################################################
SRC_PATH ?= ./simplec
MAIN_DIR ?= main
SCHEAD_DIR ?= module/schead
SERVICE_DIR ?= module/service
STRUCT_DIR ?= module/struct
TEST_DIR ?= test
TAR_PATH ?= ./Output
BUILD_DIR ?= obj # 指定一些目录
DIR = -I$(SRC_PATH)/$(SCHEAD_DIR)/include -I$(SRC_PATH)/$(SERVICE_DIR)/include \
-I$(SRC_PATH)/$(STRUCT_DIR)/include # 全局替换变量
CC = gcc
LIB = -lpthread -lm
CFLAGS = -g -Wall -O2 -std=gnu99 # 运行指令信息
define NOW_RUNO
$(notdir $(basename $())).o : $() | $$(TAR_PATH)
$$(CC) $$(CFLAGS) $$(DIR) -c -o $$(TAR_PATH)/$$(BUILD_DIR)/$$@ $$<
endef # 单元测试使用, 生成指定主函数的运行程序
RUN_TEST = $(CC) $(CFLAGS) $(DIR) --entry=$(basename $@) -nostartfiles -o \
$(TAR_PATH)/$(TEST_DIR)/$@ $(foreach v, $^, $(TAR_PATH)/$(BUILD_DIR)/$(v)) # 产生具体的单元测试程序
define TEST_RUN
$() : $$(notdir $$(basename $())).o libschead.a $() | $$(TAR_PATH)
$$(RUN_TEST) $(LIB)
endef ##################################################################################################
# .具体的产品生产 #
##################################################################################################
.PHONY:all clean cleanall all : main.out\
$(foreach v, $(wildcard $(SRC_PATH)/$(TEST_DIR)/*.c), $(notdir $(basename $(v))).out) # 主运行程序main
main.out:main.o simplec.o libschead.a libstruct.a test_sctimeutil.o
$(CC) $(CFLAGS) $(DIR) -o $(TAR_PATH)/$@ $(foreach v, $^, $(TAR_PATH)/$(BUILD_DIR)/$(v)) $(LIB) # !!!!! - 生成具体的单元测试程序 - 依赖个人维护 - !!!!!
$(eval $(call TEST_RUN, test_array.out, array.o))
$(eval $(call TEST_RUN, test_atom_rwlock.out))
$(eval $(call TEST_RUN, test_cjson.out, tstr.o))
$(eval $(call TEST_RUN, test_cjson_write.out, tstr.o))
$(eval $(call TEST_RUN, test_csv.out, tstr.o))
$(eval $(call TEST_RUN, test_json_read.out, tstr.o))
$(eval $(call TEST_RUN, test_log.out))
$(eval $(call TEST_RUN, test_scconf.out, tstr.o tree.o))
$(eval $(call TEST_RUN, test_scoroutine.out, scoroutine.o))
$(eval $(call TEST_RUN, test_scpthread.out, scpthread.o scalloc.o))
$(eval $(call TEST_RUN, test_sctimer.out, sctimer.o scalloc.o))
$(eval $(call TEST_RUN, test_sctimeutil.out))
$(eval $(call TEST_RUN, test_tstring.out, tstr.o))
$(eval $(call TEST_RUN, test_xlsmtojson.out, tstr.o)) ##################################################################################################
# 2.先产生所需要的所有机器码文件 #
################################################################################################## # 循环产生 - 所有 - 链接文件 *.o
SRC_CS = $(wildcard\
$(SRC_PATH)/$(MAIN_DIR)/*.c\
$(SRC_PATH)/$(TEST_DIR)/*.c\
$(SRC_PATH)/$(SCHEAD_DIR)/*.c\
$(SRC_PATH)/$(SERVICE_DIR)/*.c\
$(SRC_PATH)/$(STRUCT_DIR)/*.c\
)
$(foreach v, $(SRC_CS), $(eval $(call NOW_RUNO, $(v)))) # 生产 -相关- 静态库
libschead.a : $(foreach v, $(wildcard $(SRC_PATH)/$(SCHEAD_DIR)/*.c), $(notdir $(basename $(v))).o)
ar cr $(TAR_PATH)/$(BUILD_DIR)/$@ $(foreach v, $^, $(TAR_PATH)/$(BUILD_DIR)/$(v))
libstruct.a : $(foreach v, $(wildcard $(SRC_PATH)/$(STRUCT_DIR)/*.c), $(notdir $(basename $(v))).o)
ar cr $(TAR_PATH)/$(BUILD_DIR)/$@ $(foreach v, $^, $(TAR_PATH)/$(BUILD_DIR)/$(v)) ##################################################################################################
# 3.程序的收尾工作,清除,目录构建 #
##################################################################################################
$(TAR_PATH):
-mkdir -p $@/$(BUILD_DIR)
-mkdir -p $@/test/config
-cp -r $(SRC_PATH)/test/config $@/test # 清除操作
clean :
-rm -rf $(TAR_PATH)/$(BUILD_DIR)/* cleanall :
-rm -rf $(TAR_PATH)

具体可以参照simplec 项目查看, 我们抽一部分重点讲解

define NOW_RUNO
$(notdir $(basename $())).o : $() | $$(TAR_PATH)
$$(CC) $$(CFLAGS) $$(DIR) -c -o $$(TAR_PATH)/$$(BUILD_DIR)/$$@ $$<
endef

上面定义了一个语句块 NOW_RUNO. 其中语句块中除了要接收的参数可以用$(1), $(2) ..., 其它都是两个$$开头, 否则就被替换了. 使用方法就是

$(eval $(call NOW_RUNO, $(v)))

通过$eval(), $(call ) 这种套路调用. call NOW_RUNO, 后面添加都是 NOW_RUNO语句块的函数了.

这里说一个Makefile处理的潜在小问题, 当你传入参数是依赖项时候, 如果不是直接通过唯一一个参数传入进去,

那么解析的是当成多个依赖项处理.所以上面只有 $(1)做依赖项.

Makefile中 foreach语法也很好用等同于shell语法传参方式.

$(foreach v, $^, $(TAR_PATH)/$(BUILD_DIR)/$(v))
将第二个$^通过空格分隔成单个的v代替, 被替换为第三个中一部分. $(foreach ...)执行完毕最终返回一个拼接好的串

在简单补充几个函数说明 例如

$(1) => $$(notdir $$(basename $())).o <=> ./simplec/main/main.c => main.o

其中 nodir函数得到文件名, basename函数得到文件名不包过.和.后面部分.
wildcard 函数是得到指定匹配规则下的文件全路径拼接.
最后面 -rm 那些, 加了前缀 - 是为了当Makefile执行到这如果运行出错, 不停止继续前行.
通过上面Makefile最终跑起来后, 会生成一个Output目录, 再在内部生成 obj, test, ...
还是很有学习价值的. 有兴趣的可以试试.
希望通过上面讲解, 能够使你以后阅读其它更高级项目的编译文件不那么生疏. (* ̄(エ) ̄)

后记  -  突然想起了什么, 笑了笑 我自己 ...

  伽罗  -  http://music.163.com/#/artist/desc?id=21309

Makefile 跟着走快点的更多相关文章

  1. 跟着Sedgewick学算法(week 1 UnionFind)

    发现笔记转过来,没有图的~~~~~~~~~~~悲剧,给出共享笔记链接 https://www.evernote.com/pub/yanbinliu/algorithm 很久之前就在coursera看到 ...

  2. 一个五年 Android 开发者百度、阿里、聚美、映客的面试心经

    花絮 也许会有人感叹某些人的运气比较好,但是他们不曾知道对方吃过多少苦,受过多少委屈.某些时候就是需要我们用心去发现突破点,然后顺势而上,抓住机遇,那么你将会走向另外一条大道,成就另外一个全新的自我. ...

  3. 萝卜德森的sublime笔记中文翻译版

    我已经使用subliem编辑器版本2接近2个月了,并且我在其中找到了一堆有用的技巧.我发觉应该写下这些技巧,为那些对此感兴趣的人们.我会尽力的详细描述,那些看起来像魔法一样的东西,因为很多非常“酷”的 ...

  4. Java和.net对比分析

    .Net和Java是国内市场占有率最高的两门技术,对于准备学习编程语言的初学者来说,.Net和Java是初学者首先考虑的两门技术,因此很多人一遍遍的问“学.Net还是学Java”,社区中也每天都有“. ...

  5. 如何进入百度、阿里,一个6年Android老司机的面经

    花絮 也许会有人感叹某些人的运气比较好,但是他们不曾知道对方吃过多少苦,受过多少委屈.某些时候就是需要我们用心去发现突破点,然后顺势而上,抓住机遇,那么你将会走向另外一条大道,成就另外一个全新的自我. ...

  6. [LeetCode] Remove Nth Node From End of List 移除链表倒数第N个节点

    Given a linked list, remove the nth node from the end of list and return its head. For example, Give ...

  7. 熟悉HTML CSS布局模型

    HTML最难的地方来了!这个我反复了很多遍, 包括现在写博客, 也对我自己算是一种温习, 我这块怕是没办法写的很好懂, 因为我自己还不能把我学到的准确通俗易懂的表达出来, 给自己记个笔记, 以后再来一 ...

  8. 菜鸟的Python学习之路(流水账)

    揭开Python的面纱 开始是因为别人说Python简单才开始学的,然后那段时间刚考完研,也没什么事,就多少瞅了瞅,然后发现语法的确简单很多,或者说简洁更合适. 当时看的是简明Python教程,没用多 ...

  9. CSS补充之--页面布局、js补充,dom补充

    CSS补充之--页面布局 主站一:(下面是一个大致的模板) <div class="pg-header"> <div style="width: 120 ...

随机推荐

  1. hdu 1528 Card Game Cheater (二分匹配)

    Card Game Cheater Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others ...

  2. 【刷题】BZOJ 3510 首都

    Description 在X星球上有N个国家,每个国家占据着X星球的一座城市.由于国家之间是敌对关系,所以不同国家的两个城市是不会有公路相连的. X星球上战乱频发,如果A国打败了B国,那么B国将永远从 ...

  3. 【以前的空间】bzoj 1052 [HAOI2007]覆盖问题

    这道题的思路挺简单的……就是可以证明如果要覆盖一个区域内的点,那么一定有一个正方形在这“区域内的点所围成的最大矩形的四个角中的一个”(不要吐槽很多的“的”……),对于长度r是否可以覆盖整个区域内的点, ...

  4. [HNOI2012][BZOJ2732] 射箭 [二分+半平面交]

    题面 BZOJ题面 思路 半平面交代码讲解戳这里,用的就是这道题 我们射箭的函数形如$y=Ax^2+Bx$ 考虑每一个靶子$(x_0,y_1,y_2)$,实际上是关于$A,B$的不等式限制条件 我们只 ...

  5. 【国家集训队】聪聪可可 ——树形DP

    感觉是一道很妙的树形DP题,充分利用到了树的性质(虽然说点分治也可以做,,,,但是本蒟蒻不会啊) 然而某Twilight_Sx大佬表示这道题真的非常水,,,本蒟蒻也只能瑟瑟发抖了 本蒟蒻表示还是要经过 ...

  6. Docker-端口映射

    Docker-端口映射 Docker端口映射 docker容器在启动的时候,如果不指定端口映射参数,在容器外部是无法通过网络来访问容器内的网络应用和服务的. 亦可使用Dockerfile文件中的EXP ...

  7. BZOJ4896 [Thu Summer Camp2016]补退选 【trie树】

    题目链接 BZOJ4896 题解 \(thu\)怎么那么喜欢出\(trie\)树的题... 我们当然可以按题意模拟建\(trie\) 询问的时候,由于存在删除操作,不满足单调性,不能直接二分答案 我们 ...

  8. 使用snmp4j实现Snmp功能(二)

    相关链接:Snmp学习笔记使用snmp4j实现Snmp功能(一)使用snmp4j实现Snmp功能(二)使用snmp4j实现Snmp功能(三) 前一篇文章讲了如何用snmp4j实现set和get的功能, ...

  9. HDU 1715 大数java

    大菲波数 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submis ...

  10. ubuntu server安装kvm

    参考资料: 1. https://help.ubuntu.com/community/KVM 2.http://wiki.ubuntu.org.cn/Kvm%E6%95%99%E7%A8%8B 3.h ...