Win32 环境下的堆栈
原文已经找不到,作者应该是:http://blog.csdn.net/slimak 但是没有找到此文,其中丢了2幅图
简介
在Win32环境下利用调试器调试应用程序的时候经常要和堆栈(Stack)打交道,尤其是在需要手工遍历堆栈(Manually Walking Stack)的时候我们需要对堆栈的工作过程有一个比较清晰的了解.接下来的这些文字将通过一个例子程序详细的讲解堆栈的工作过程.
关键字
调试堆栈 Stack Stack-Frame
目录
1.堆栈是什么?
2.堆栈里面放的都是什么信息?
3.堆栈是在什么时候被建立起来的?它的默认大小是多少?
4.默认才1M??那要是我的程序使用超过了1M的堆栈怎么办?
5.什么叫Stack Frame?
6.在一次函数调用中,堆栈是如何工作的?
7.老大,结合一个例子讲讲吧?
1.堆栈是什么?
从内存管理角度看,堆栈是就是一块连续的内存空间,对它的操作采用先入后出的规则,他的生长方向与内存的生长方向正好相反,也就是说它是从高地址向低地址生长.
从Win32程序内部的角度看,每一个线程有自己的堆栈,它主要用来给线程提供一个暂时存放数据的区域,程序使用POP/PUSH指令来对堆栈进行操作.
2.堆栈里面放的都是什么信息?
堆栈中存放的信息包括:
当前正在执行的函数的局部变量;
函数返回地址;
该函数的上层函数传给该函数的参数;
EBP的值;
一些通用寄存器(EDI,ESI…)的值。
注意这里提到的正在执行的函数,比如有下面的一段C代码:
void B()
{
printf(“B\n”);
}
void A()
{
B();
}
那么当程序执行到B函数的printf函数的时候我们说正在执行的函数包括A和B而不仅仅是B函数,这一点需要注意.
3.堆栈是在什么时候被建立起来了?它的默认大小是多少?
堆栈是在我们的main主函数被系统调用之前被建立起来的,对于非主线程它是在线程被建立之前创建的,
它的默认大小是1M,
如果需要修改堆栈的大小的话可以在VC6++中通过使用/STACK编译项实现:
#pragmacomment(linker,“/STACK:2048,1024″)//预约(Reserve)2M,提交(Commit)1M
关于预约(Reserve)和提交(Commit)的概念请参看”Programming Applications for Microsoft Windows“( Jeffrey Richter,Chapter 15Using Virtual Memory in Your Own Applications)
4.默认才1M??那要是我的程序使用超过了1M的堆栈怎么办?
系统通过使用异常捕获(Exception Handling)机制来捕获应用程序企图去访问超过该程序提交(Commit)的堆栈范围这种异常,假如你程序预约了2M并且提交了1M大小的堆栈,那么当你的程序企图访问超过1M的范围的时候会产生一个异常并且被系统捕获,系统会帮你继续从另外1M预约的内存中提交内存来满足你的需求,如果你要求提交的大小甚至超过了2M(你一开始预约的大小)在 NT系统下(98除外)系统也会尝试去分配(allocate)内存来满足你,但是系统并不保证分配会成功
5.什么叫Stack Frame?
Stack Frame这个词你可以在各种各样的汇编书籍中看到,到底它表示什么意思呢?也许你看完文章的后半部分就会明白,在此我们先给它一个定义,你看完整篇文章在回过头来回味一下就会知道它的确切含义了,Stack Frame是堆栈中的一块区域,它保存着一个函数的返回地址,和该函数内部使用的局部数据(Local Data),它是由函数入口处的SUB ESP,48h之类的语句来建立的.
6.在一次函数调用中,堆栈是如何工作的?
假设我们的主角叫A函数…
a.首先上级函数传给A函数的参数被压入堆栈中(至于是谁来做这个压栈操作取决于A函数的调用方式:是__stdcall, __cdecl还是其他);
b.然后是返回地址(A函数执行完后接下来程序继续执行的地址)入栈;
c.接下来是当前的EBP;
d.如果A函数有局部变量,就在堆栈中开辟相应的空间以构造那些变量变量(A函数执行结束,这些局部变量的内容将被忽略/遗弃,但是不被清除,比如A函数中有一个变量int m存在于地址0×0012FFCC处,函数结束时9依然存在于0×0012FFCC处没有被清除,但是此时它已经没有任何意义了,
e.在函数返回的时候,弹出EBP,恢复堆栈到函数调用前的地址,弹出返回地址到EIP以继续执行程序。
7.结合一个例子
下面就是我们要拿来做模特的代码,程序很简单,wWinMain调用AFunc,AFunc再调用BFunc,下面的讲解过程中我们要观摩这个程序的汇编代码形式,可以通过在VC6++该工程的Debug模式中按F5然后Ctrl+Tab做到,我想这对于Win32程序员应该不是难事.
int BFunc(int i,int j)
{
int m = 1;
int n = 2;
m = i;
n = j;
return m;
}
int AFunc(int i,int j)
{
int m = 3;
int n = 4;
m = i;
n = j;
BFunc(m,n);
return 8;
}
int APIENTRY wWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
AFunc(5,6);
return 0;
}
步骤1.我们从wWinMain调用AFunc函数开始
wWinMain调用AFunc的时候,先把参数压栈(至于为什么压栈顺序是6,5而不是5,6请参看附录.注解1)参数压栈结束后此时ESP = 0×0012FEDC,EBP = 0×0012FF30,
这是进入AFunc函数之前的堆栈形势图:
图 1
步骤2.记住进入AFcun函数之前的ESP,EBP的值,然后我们进入AFunc…
为方便大家观摩,先把AFunc函数的全貌贴出来
29: int AFunc(int i,int j)
30: {
004010D0push ebp ;先把EBP入栈保存
004010D1mov ebp,esp ;再把此时的ESP赋给EBP,这样EBP就可以拿来访问本函数的局部变量
004010D3sub esp,48h ;为AFunc函数在堆栈重开辟一块空间,一般来说开辟的空间大小是40+
;函数内所有局部变量的大小;
004010D6push ebx ;通用寄存器入栈,算保存现场吧
004010D7push esi
004010D8push edi
004010D9lea edi,[ebp-48h]
004010DCmov ecx,12h
004010E1mov eax,0CCCCCCCCh
004010E6rep stos dword ptr [edi]
31: int m = 3;
004010E8mov dword ptr [ebp-4],3 ;为什么局部变量m位于ebp-3处?
32: int n = 4;
004010EFmov dword ptr [ebp-8],4;为什么局部变量n位于ebp-8处?
33:
34: m = i;
004010F6mov eax,dword ptr [ebp+8] ;ebp+8处存的是什么?
004010F9mov dword ptr [ebp-4],eax
35: n = j;
004010FCmov ecx,dword ptr [ebp+0Ch] ;ebp+0ch处存的是什么?
004010FFmov dword ptr [ebp-8],ecx
36:
37: BFunc(m,n);
00401102mov edx,dword ptr [ebp-8] ;AFunc调用BFunc之前先把传给BFunc的参数入栈
00401105push edx
00401106mov eax,dword ptr [ebp-4]
00401109push eax
0040110Acall @ILT+25(BFunc) (0040101e)
0040110Fadd esp,8 ;这个出栈操作为什么?
38:
39:return 8;
00401112mov eax,8
40: }
00401117pop edi ;恢复现场
00401118pop esi
00401119pop ebx
0040111Aadd esp,48h ;收回函数一开始在栈中开辟的空间
;对应于一开始的sub esp,48h
0040111Dcmp ebp,esp
0040111Fcall __chkesp (00401220)
00401124mov esp,ebp
00401126pop ebp;恢复调用前的EBP
00401127ret
下面我们要花些篇幅详细的解释AFunc函数执行过程堆栈(主要是ESP,EBP)的变化情况:
29: int AFunc(int i,int j)
30: {
004010D0 push ebp
004010D1 mov ebp,esp
004010D3 sub esp,48h
004010D6 push ebx
004010D7 push esi
004010D8 push edi
;
; 上面几行代码叫做prolog,可以理解成”序曲,开始部分”,与之对应的叫epilog(结束曲,结束部分)对于
; prolog需要逐行解释一下:
;
; 004010D0 PUSH EBP
; 将进入AFunc函数之前的EBP的值入栈保存,这时候的EBP相当于是AFunc上级函数
; 的一个现场信息,所以需要保存起来,以便于AFunc返回后上级函数可以恢复EBP使其指向其调用
; AFunc之前的堆栈位置(当然,这还需要靠恢复ESP来协助达到这一目的),该语句执行完之后堆栈将
; 变成下面这个样子
;
图 2
; 在这里要解释一下什么时候”AFunc结束之后的返回地址”入栈了?导致它入栈的语句就是
; CALL @ILT+20(AFunc) (00401019)
; 也就是说是CALL指令干的
;
; 004010D1 MOV EBP,ESP
; ESP赋给EBP,这样EBP就可以拿来访问本函数的局部变量
图 3
; 004010D3 SUB ESP,48h AFunc函数中有两个int型的变量所以开辟的空间大小是
; 40+2*sizeof(int),我暂时还没有找到正式文档中对于此大小
; 计算的公式.注意:ESP-48h后开辟的新的堆栈中的这块空间就是
; 大名鼎鼎的Stack Frame.
; 004010D6 PUSH EBX 我们知道通用寄存器有时候在程序运算的时候可以用来存放
; 临时结果,如果此结果有必要的话也是需要作为现场信息被保存在
; 堆栈中的.
; 004010D7 PUSH ESI
; 004010D8 PUSH EDI
图 4
; 从上面的图解我们很容易看出在进入AFunc函数执行完prolog之后ESP和EBP指示出了堆栈中
; 存放的当前执行函数的信息(绿色部分,其上级函数的堆栈信息由亮绿色表示,呵呵,我可能有一点色
; 弱所以那到底是不是亮绿色我也不是很确定,夜深人静也没人可问…)
004010D9 lea edi,[ebp-48h]
004010DC mov ecx,12h
004010E1 mov eax,0CCCCCCCCh
004010E6 rep stos dword ptr [edi]
31: int m = 3;
004010E8 mov dword ptr [ebp-4],3;函数的局部变量放置在EBP的负偏移处(Negative
; Offset)也就是向低地址方向(当然,当然,这是针对该函数使用
; 了标准的Stack Frame,如果代码被编译器作了优化了那么你
; 很可能就要遇到FPO这个概念,这可能需要另外写一篇文章
; 来解释,所以这里假设我们的函数使用的是标准的Stack
; Frame)
32: int n = 4;
004010EF mov dword ptr [ebp-8],4;同上
图 5
33:
34: m = i;
004010F6 mov eax,dword ptr [ebp+8];从上图中很容易看出来dword ptr [ebp+8]里面放的是
; 上级函数传给AFunc的第一个参数,这里用ebp+8来访问
; 参数说明上级传给下级函数的参数是放在下级函数
; 的EBP的正向偏移位置处(Positive Offset)
004010F9 mov dword ptr [ebp-4],eax;将参数的值赋给局部变量
35: n = j;
004010FC mov ecx,dword ptr [ebp+0Ch];同上
004010FF mov dword ptr [ebp-8],ecx;同上
图 6
步骤3.现在AFcun函数要调用BFunc了…
这是调用前的准备工作:
a.参数被压栈;
b.CALL指令导致返回地址0040110F入栈;
37: BFunc(m,n);
00401102 mov edx,dword ptr [ebp-8]
00401105 push edx
00401106 mov eax,dword ptr [ebp-4]
00401109 push eax
0040110A call @ILT+25(BFunc) (0040101e)
0040110F add esp,8
图 7
; 这和一开始wWinMain调用AFunc是差不多的过程
38:
39: return 8;
00401112 mov eax,8
40: }
00401117 pop edi
00401118 pop esi
00401119 pop ebx
0040111A add esp,48h
0040111D cmp ebp,esp
0040111F call __chkesp (00401220)
00401124 mov esp,ebp
00401126 pop ebp
00401127 ret
步骤4.进入BFcun函数之后堆栈的变化…
老规矩,我们先通篇看看BFunc在VC6++中的汇编代码:
18: int BFunc(int i,int j)
19: {
00401090 push ebp
00401091 mov ebp,esp
00401093 sub esp,48h
00401096 push ebx
00401097 push esi
00401098 push edi
00401099 lea edi,[ebp-48h]
0040109C mov ecx,12h
004010A1 mov eax,0CCCCCCCCh
004010A6 rep stos dword ptr [edi]
20: int m = 1;
004010A8 mov dword ptr [ebp-4],1
21: int n = 2;
004010AF mov dword ptr [ebp-8],2
22:
23: m = i;
004010B6 mov eax,dword ptr [ebp+8]
004010B9 mov dword ptr [ebp-4],eax
24: n = j;
004010BC mov ecx,dword ptr [ebp+0Ch]
004010BF mov dword ptr [ebp-8],ecx
25:
26: return m;
004010C2 mov eax,dword ptr [ebp-4]
27: }
004010C5 pop edi
004010C6 pop esi
004010C7 pop ebx
004010C8 mov esp,ebp
004010CA pop ebp
004010CB ret
; 先看看BFunc的prolog:
18: int BFunc(int i,int j)
19: {
00401090 push ebp
00401091 mov ebp,esp
00401093 sub esp,48h
00401096 push ebx
00401097 push esi
00401098 push edi
图 8
; 这个时候BFunc的堆栈信息也搭建好了(灰色部分)
20: int m = 1;
004010A8 mov dword ptr [ebp-4],1;没什么新意的操作,和AFunc中发生的一模一样
21: int n = 2;
004010AF mov dword ptr [ebp-8],2;没新意
图 9
22:
23: m = i;
004010B6 mov eax,dword ptr [ebp+8];没新意
004010B9 mov dword ptr [ebp-4],eax
24: n = j;
004010BC mov ecx,dword ptr [ebp+0Ch];没新意
004010BF mov dword ptr [ebp-8],ecx
25:
26: return m;
004010C2 mov eax,dword ptr [ebp-4];函数的返回值是放在EAX里面返回的,如果说一个个函
; 数之间是行星的话EAX就是神5那载着杨天人的返回
; 舱了.
27: }
; 我们把重点放在BFunc函数返回时执行的这些指令上(epilog)
004010C5 pop edi
004010C6 pop esi
004010C7 pop ebx
004010C8 mov esp,ebp
004010CA pop ebp
004010CB ret
图 10
图 11
; 此时你会发现图11与图 7时的堆栈情况完全(ESP,EBP的值相同)一样,也就是说调用完BFunc函数后
; 堆栈恢复到了调用前的状态.
0040110F add esp,8;注意BFunc执行完返回AFunc后AFunc将通过改变ESP将先前传给BFunc
; 的参数出栈,但不清空.
图12
就此AFunc调用BFunc函数结束了,接下来堆栈继续重演着:父函数调用子函数,子函数执行结束后返回.然后父函数又作为别人的子函数,执行结束,返回…..
附录
注解1
因为默认C/C++函数的调用约定是__cdecl,这种调用约定参数是从右到左压栈的,Windows提供的函数大部分是__stdcall的调用约定,符合该约定的函数在传参数的时候也是从右到左压栈.
参考书目
[1] Jeffrey Richter,”Programming Applications for Microsoft Windows4rd”.( Microsoft Press,1999)
[2] Intel Architecture Software Developer Manual
[3] Randy Kath. “The Win32 Debugging API.” MSDN
相关文章:

在VMWare中配置SQLServer2005集群 Step by Step(四)——集群安装
在VMWare 中配置集群 1. 进入command 命令窗口执行以下命令,创建仲裁磁盘和共享数据磁盘 vmware-vdiskmanager.exe -c -s 200Mb -a lsilogic -t 2 F:\VM\Share\Windows\SQLServer\quorum.vmdk vmware-vdiskmanager.exe -c -s 4Gb -a lsilogic -t 2 F:\VM\Share\Wind…
口罩检测识别率惊人,这个Python项目开源了
作者 | 一颗小树x,CSDN 博主编辑 | 唐小引来源 | CSDN 博客昨天在 GitHub 上看到一个有趣的开源项目,它能检测我们是否有戴口罩,跑起程序测试后,发现识别率挺高的,也适应不同环境,于是分享给大家。首先感谢…

CentOS搭建msmtp+mutt实现邮件发送
1:搭建配置msmtp下载msmtp包:官方地址:http://msmtp.sourceforge.net/download.html编译,安装(官方下载的包为tar.xz格式):#xz -d msmtp-1.6.3.tar.xz #tar -xvf msmtp-1.6.3.tar #cd msmtp-1.6.3 #./configure --prefix /opt/app…
Linux环境下的堆栈--调试C程序
完整的调试过程,跟踪堆栈变化,32位下。 注意64位和此不同。 a.c代码: #include <stdio.h> int main() { AFunc(5,6);return 0; } int BFunc(int i,int j) {int m 1;int n 2;m i;n j; return m; }int AFunc(int i,int j) {…
听说过代码洁癖,Bug洁癖怎么解?
来源 | Python编程时光(ID: Cool-Python)当我们写的一个脚本或程序发生各种不可预知的异常时,如果我们没有进行捕获处理的时候,通常都会致使程序崩溃退出,并且会在终端打印出一堆 密密麻麻 的 traceback 堆栈信息来告诉…

POJO、VO、PO、FormBean区别:
首先讲一下四者的概念 POJO:Pure Old Java Object,符合Java Bean属性规范的简单Java对象,通常也称为VO(Value Object,值对象)。 VO:就是POJO; PO: Persistent Object,持久化对…

oracle中的sql%rowcount,sql%found、sql%notfound、sql%rowcount和sql%isopen
Oracle 存储过程 删除表记录时删除不存在的记录也是显示删除成功 create or replace procedure delDept(p_deptno in dept.deptno%type) is begindelete from dept where deptnop_deptno;dbms_output.put_line(部门删除成功...);exception when others thendbms_output.put_lin…

linux平台的链接与加载
原文是上下两篇 链接与加载(上) — 静态链接链接与加载(下) — 动态链接 为观看方便,现在合并起来。 一.静态链接 示例程序 我们先看一个简单的示例程序,代码如下: /*main.c*/int u 333;int sum(int, int);int main(int argc, char* argv…
预训练模型ProphetNet:根据未来文本信息进行自然语言生成
作者 | 刘大一恒、齐炜祯、晏宇、宫叶云、段楠、周明来源 | 微软研究院AI头条(ID:MSRAsia)编者按:微软亚洲研究院提出新的预训练模型 ProphetNet,提出了一种新的自监督学习目标——同时预测多个未来字符,在序列到序列的…

模拟进程管理小结,编码规范的重要性
废话不多说了,省的又有衰人找我麻烦。希望我讨厌的,和讨厌我的少来骚扰我,由衷的感谢它们。 我不回那些骚扰,是因为我见到名字就直接删了,看都懒的看了。也别怪我粗鲁,因为我一向是对什么人说什么话 的&…

JSPServlet路径问题
2019独角兽企业重金招聘Python工程师标准>>> 如果带WebRoot,那么js、css、img都应该放到WebRoot目录下,否则访问会有问题。千万不要放在WEB-INF下,因为WEB-INF下的内容只有服务器转发可以访问到,出于安全考虑。 如果不…

Git学习教程(六)Git日志
第六课 Git 日志 内容提要:浏览项目历史,查询指定提交内容,图形化显示分枝和合并...git log是git中最常用的一个命令,执行之后,会显示该项目的提交历史。如果命令不加任何参数,那么就会显示目前所在分枝上&…

汇编包含C代码
反汇编的时候带上C代码便于观察 比较三元表达式和if else的差异 a1.c #include <stdio.h> int main(void) { int a1;int b2;int c0;a (b>c)?1:0;return 0;} a2.c #include <stdio.h> int main(void) { int a1;int b2;int c0;if(b>c){a1;}else{a0;…
无需3D运动数据训练,最新人体姿势估计方法达到SOTA | CVPR 2020
作者 | Muhammed Kocabas译者 | 刘畅出品 | AI科技大本营(ID:rgznai100)人体的运动对于理解人的行为是非常重要的。尽管目前已经在单图像3D姿势和动作估计方面取得了进展,但由于缺少用于训练的真实的3D运动数据,因此现有的基于视频…

Linux内核跟踪之trace框架分析【转】
转自:http://blog.chinaunix.net/uid-20543183-id-1930846.html------------------------------------------本文系本站原创,欢迎转载!转载请注明出处:http://ericxiao.cublog.cn/------------------------------------------一: 前言本文主要是对trace的框架做详尽…
写给Python开发者:机器学习十大必备技能
作者 | Pratik Bhavsar译者 | 明明如月,编辑 | 夕颜来源 | CSDN(ID:CSDNnews)有时候,作为一个数据科学家,我们常常忘记了初心。我们首先是一个开发者,然后才是研究人员,最后才可能是数学家。我…

Linux环境程序栈溢出原理
当在缓冲区中输入过多的数据时,缓冲区溢出就会发生,C语言提供了多种方法,可以使在缓冲区中输入的数据比预期的多。 局部变量可以被分配到栈上。这就意味着在栈的某个地方有一个固定大小的缓冲区。 而栈是向下增长的,而且一些重要…

[翻译]Joomla 1.5架构(十一) model 包
这个包包含了跟数据表交互的所有相关类 JModel This abstract class is the base class for all Joomla! data access objects. 所有数据访问类的抽象基类。 以下的类都分别实现对不同表的访问,不再翻译了。 Adapter Folder JModelCategory This is a data access …

度量快速开发平台端口映射的介绍
度量快速开发平台在客户中部署的时候,可能会想内网与外网用户同时使用。一般情况下,服务端都是部署在内网的,那外网用户要访问,就可能用到端口映射的功能。端口映射基本都是在路由器上进行。下面就是几个常用的路由器上的设置方法…

为什么栈和堆的生长方向不一样
栈的生长方向 8051的栈是向高地址增长,INTEL的8031、8032、8048、8051系列使用向高地址增长的堆栈;但同样是INTEL,在x86系列中全部使用向低地址增长的堆栈。其他公司的CPU中除ARM的结构提供向高地址增长的堆栈选项外,多数都是使用…
简单粗暴理解与实现机器学习之逻辑回归:逻辑回归介绍、应用场景、原理、损失以及优化...
作者 | 汪雯琦责编 | Carol来源 | CSDN 博客出品 | AI科技大本营(ID:rgznai100)学习目标知道逻辑回归的损失函数知道逻辑回归的优化方法知道sigmoid函数知道逻辑回归的应用场景应用LogisticRegression实现逻辑回归预测知道精确率、召回率指标的区别知道如…

生命的脆弱——悼念朋友
生命的脆弱让我们敲希望的钟啊多少祈祷在心中让大家看不到失败叫成功永远在让地球忘记了转动啊四季少了夏秋冬让宇宙关不了天窗叫太阳不西沉让欢喜代替了哀愁啊微笑不会再害羞让时光懂得去倒流叫青春不开溜让贫穷开始去逃亡啊快乐健康留四方让世界找不到黑暗幸福像花开放让大家…

VMware Tools手动下载
2019独角兽企业重金招聘Python工程师标准>>> VMware自己下载VMware Tools非常慢。你可以自己手动下载它。 下载地址为: version: 8.8.2 http://softwareupdate.vmware.com/cds/vmw-desktop/ws/8.0.3/ 选择最新的build,例如: http:…

Linux查看多核CPU利用率
1.top 使用权限:所有使用者 使用方式:top [-] [d delay] [q] [c] [S] [s] [i] [n] [b] 说明:即时显示process的动态 d :改变显示的更新速度,或是在交谈式指令列( interactive command)按s q :没有任何延迟的显示速度…

仓央嘉措《那一天,那一月,那一年,那一世》
那一天, 我闭目在经殿的香雾中, 蓦然听见你颂经中的真言; 那一月, 我摇动所有的经筒, 不为超度, 只为触摸你的指尖; 那一年, 磕长头匍匐在…
AI+大数据助力抗疫,带你认识百度地图的新玩法!
作者 | Aholiab责编 | Carol出品 | AI科技大本营(ID:rgznai100)“喂,你好,我是百度地图的客服,请问是xx店铺对吗?”“嗯,什么事?”“您家在疫情期间还照常营业,对吗&…

Coursera Machine Learning 作业提交问题
关于作业提交问题的解决办法 Octave 4.0.0无法正常提交 解决办法:打两个补丁 补丁1:平台通用补丁2:Win,Linux or Mac 注:补丁文件中有安装说明

Linux查看进程内存状况
查看全部进程 通过top或ps -ef | grep 进程名 得到进程的PID。该命令可以提供进程状态、文件句柄数、内存使用情况等信息。 #pa aux 先查看进程 nginx的工作进程是5757 pmap命令 可以显示一个或多个进程所使用的内存数量。你可以使用这个工具来了解服务器上的某个进程分配…
用于小型图形挖掘研究的瑞士军刀:空手道俱乐部的图表学习Python库
作者 | Benedek Rozemberczki译者 | 天道酬勤 责编 | Carol出品 | AI科技大本营(ID:rgznai100)空手道俱乐部(Karate Club)是NetworkX Python软件包的无监督机器学习扩展库。详细可以参阅此处的文档:https://github.com…

电子商务创造的第二次产业机会
即将迎来冬至节气的这个周末,天寒地冻,却是电子商务的饕餮之季。淘宝网商交易大会刚刚在成都落下帷幕,而比网货交易会更令业界期待的“2009中国电子商务创新发展高峰论坛”也在北京顺利召开。大会由国内最大的电子商务软件及服务提供商ShopEx…