33.2 std::list

std::list是标准的双向链表,每一个元素包含两个指针,分别前一个和后一个元素。 这意味着每个元素的的内存空间大小要相应的扩大2个字。(在32位环境下为8字节,64位环境下为16字节) std::list也是一个循环链表,也就是说最后一个元素包含一个指向第一个元素的指针,反之亦然。 C++ STL只是为你希望做为链表使用的结构体增加前向和后向指针。 下面我们构造一个包含两个简单变量的结构体,将其存储在链表中。 虽然C++标准中并没有规定如何实现list,但是MSVC和GCC的实现比较相似,因此下面仅通过一段源代码来展示。

  1. #include <stdio.h>
  2. #include <list>
  3. #include <iostream>
  4. struct a
  5. {
  6. int x;
  7. int y;
  8. };
  9. struct List_node
  10. {
  11. struct List_node* _Next;
  12. struct List_node* _Prev;
  13. int x;
  14. int y;
  15. };
  16. void dump_List_node (struct List_node *n)
  17. {
  18. printf ("ptr=0x%p _Next=0x%p _Prev=0x%p x=%d y=%d\n", n, n->_Next, n->_Prev, n->x, n->y);
  19. };
  20. void dump_List_vals (struct List_node* n)
  21. {
  22. struct List_node* current=n;
  23. for (;;)
  24. {
  25. dump_List_node (current);
  26. current=current->_Next;
  27. if (current==n) // end
  28. break;
  29. };
  30. };
  31. void dump_List_val (unsigned int *a)
  32. {
  33. #ifdef _MSC_VER
  34. // GCC implementation doesn’t have "size" field
  35. printf ("_Myhead=0x%p, _Mysize=%d\n", a[0], a[1]);
  36. #endif
  37. dump_List_vals ((struct List_node*)a[0]);
  38. };
  39. int main()
  40. {
  41. std::list<struct a> l;
  42. printf ("* empty list:\n");
  43. dump_List_val((unsigned int*)(void*)&l);
  44. struct a t1;
  45. t1.x=1;
  46. t1.y=2;
  47. l.push_front (t1);
  48. t1.x=3;
  49. t1.y=4;
  50. l.push_front (t1);
  51. t1.x=5;
  52. t1.y=6;
  53. l.push_back (t1);
  54. printf ("* 3-elements list:\n");
  55. dump_List_val((unsigned int*)(void*)&l);
  56. std::list<struct a>::iterator tmp;
  57. printf ("node at .begin:\n");
  58. tmp=l.begin();
  59. dump_List_node ((struct List_node *)*(void**)&tmp);
  60. printf ("node at .end:\n");
  61. tmp=l.end();
  62. dump_List_node ((struct List_node *)*(void**)&tmp);
  63. printf ("* let’s count from the begin:\n");
  64. std::list<struct a>::iterator it=l.begin();
  65. printf ("1st element: %d %d\n", (*it).x, (*it).y);
  66. it++;
  67. printf ("2nd element: %d %d\n", (*it).x, (*it).y);
  68. it++;
  69. printf ("3rd element: %d %d\n", (*it).x, (*it).y);
  70. it++;
  71. printf ("element at .end(): %d %d\n", (*it).x, (*it).y);
  72. printf ("* let’s count from the end:\n");
  73. std::list<struct a>::iterator it2=l.end();
  74. printf ("element at .end(): %d %d\n", (*it2).x, (*it2).y);
  75. it2--;
  76. printf ("3rd element: %d %d\n", (*it2).x, (*it2).y);
  77. it2--;
  78. printf ("2nd element: %d %d\n", (*it2).x, (*it2).y);
  79. it2--;
  80. printf ("1st element: %d %d\n", (*it2).x, (*it2).y);
  81. printf ("removing last element...\n");
  82. l.pop_back();
  83. dump_List_val((unsigned int*)(void*)&l);
  84. };

GCC

我们以GCC为例开始,当我们运行这个例子,可以看到屏幕上打印出了大量输出,我们依次对其进行分析。

  1. * empty list:
  2. ptr=0x0028fe90 _Next=0x0028fe90 _Prev=0x0028fe90 x=3 y=0

我们可以看到一个空的链表,它由一个元素组成,其中x和y中包含有垃圾数据。前向和后向指针均指向自身。

这一时刻的.begin和.end迭代器两者相等。 当我们压入3个元素之后,链表的内部状态将变为:

  1. * 3-elements list:
  2. ptr=0x000349a0 _Next=0x00034988 _Prev=0x0028fe90 x=3 y=4
  3. ptr=0x00034988 _Next=0x00034b40 _Prev=0x000349a0 x=1 y=2
  4. ptr=0x00034b40 _Next=0x0028fe90 _Prev=0x00034988 x=5 y=6
  5. ptr=0x0028fe90 _Next=0x000349a0 _Prev=0x00034b40 x=5 y=6

最后一个元素依然位于0x28fe90,只有当链表被销毁的时候它才会移动。x和y中依然包含有垃圾数据。虽然和链表中的最后一个元素中的x和y拥有相同的值,但这并不能说明这些值有意义。 下面这幅图说明了链表中的三个元素如何在内存中存储

变量l始终指向第一个链表节点 .begin()和.end()迭代器不指向任何东西,也并不出现在内存中,但是当适当的方法被调用的时候将返回指向这些节点的指针。(这句话是说.begin()和.end()并不是变量,而是函数,该函数返回迭代器。) 链表中包含一个垃圾元素在链表的实现中是非常流行的方式,如果不包含它,很多操作都将变得复杂和低效。 迭代器实质上是一个指向链表节点的指针。list.begin()和list.end()仅仅返回迭代器。

  1. node at .begin:
  2. ptr=0x000349a0 _Next=0x00034988 _Prev=0x0028fe90 x=3 y=4
  3. node at .end:
  4. ptr=0x0028fe90 _Next=0x000349a0 _Prev=0x00034b40 x=5 y=6

实际上链表是循环的非常有用:包含一个指向第一个链表元素的指针,类似l变量,这将有利于快速获取指向最后一个元素的指针,而不必遍历整个链表。这样也有利于快速的在链表尾部插入元素。 –和++操作符只是将当前的迭代器的值设置为current_node->prev、current_node->next。反向迭代器只是以相反的方向做相似的工作。

迭代器的*操作符返回指向节点结构指针,也就是用户结构的起始位置,如,指向第一个结构体元素x的指针。 List的插入和删除比较简单,只需要分配新节点同时给指针设置有效值。 这就是元素被删除后,迭代器会失效的原因,它依然指向已经被释放的节点。当然,迭代器指向的被释放的节点的数据是不能再使用的。

GCC的实现中没有存储链表的大小,由于没有其他方式获取这些信息,.size()方法需要遍历整个链表统计元素个数,因此执行速度较慢。这一操作的时间复杂度为O(n),即消耗时间多少和链表中元素个数成正比。

Listing34.7: GCC 4.8.1 -O3 -fno-inline-small-functions

  1. main proc near
  2. push ebp
  3. mov ebp, esp
  4. push esi
  5. push ebx
  6. and esp, 0FFFFFFF0h
  7. sub esp, 20h
  8. lea ebx, [esp+10h]
  9. mov dword ptr [esp], offset s ; "* empty list:"
  10. mov [esp+10h], ebx
  11. mov [esp+14h], ebx
  12. call puts
  13. mov [esp], ebx
  14. call _Z13dump_List_valPj ; dump_List_val(uint *)
  15. lea esi, [esp+18h]
  16. mov [esp+4], esi
  17. mov [esp], ebx
  18. mov dword ptr [esp+18h], 1 ; X for new element
  19. mov dword ptr [esp+1Ch], 2 ; Y for new element
  20. call _ZNSt4listI1aSaIS0_EE10push_frontERKS0_ ; std::list<a,std::allocator<a
  21. >>::push_front(a const&)
  22. mov [esp+4], esi
  23. mov [esp], ebx
  24. mov dword ptr [esp+18h], 3 ; X for new element
  25. mov dword ptr [esp+1Ch], 4 ; Y for new element
  26. call _ZNSt4listI1aSaIS0_EE10push_frontERKS0_ ; std::list<a,std::allocator<a
  27. >>::push_front(a const&)
  28. mov dword ptr [esp], 10h
  29. mov dword ptr [esp+18h], 5 ; X for new element
  30. mov dword ptr [esp+1Ch], 6 ; Y for new element
  31. call _Znwj ; operator new(uint)
  32. cmp eax, 0FFFFFFF8h
  33. jz short loc_80002A6
  34. mov ecx, [esp+1Ch]
  35. mov edx, [esp+18h]
  36. mov [eax+0Ch], ecx
  37. mov [eax+8], edx
  38. loc_80002A6: ; CODE XREF: main+86
  39. mov [esp+4], ebx
  40. mov [esp], eax
  41. call _ZNSt8__detail15_List_node_base7_M_hookEPS0_ ; std::__detail::
  42. _List_node_base::_M_hook(std::__detail::_List_node_base*)
  43. mov dword ptr [esp], offset a3ElementsList ; "* 3-elements list:"
  44. call puts
  45. mov [esp], ebx
  46. call _Z13dump_List_valPj ; dump_List_val(uint *)
  47. mov dword ptr [esp], offset aNodeAt_begin ; "node at .begin:"
  48. call puts
  49. mov eax, [esp+10h]
  50. mov [esp], eax
  51. call _Z14dump_List_nodeP9List_node ; dump_List_node(List_node *)
  52. mov dword ptr [esp], offset aNodeAt_end ; "node at .end:"
  53. call puts
  54. mov [esp], ebx
  55. call _Z14dump_List_nodeP9List_node ; dump_List_node(List_node *)
  56. mov dword ptr [esp], offset aLetSCountFromT ; "* let’s count from the begin:"
  57. call puts
  58. mov esi, [esp+10h]
  59. mov eax, [esi+0Ch]
  60. mov [esp+0Ch], eax
  61. mov eax, [esi+8]
  62. mov dword ptr [esp+4], offset a1stElementDD ; "1st element: %d %d\n"
  63. mov dword ptr [esp], 1
  64. mov [esp+8], eax
  65. call __printf_chk
  66. mov esi, [esi] ; operator++: get ->next pointer
  67. mov eax, [esi+0Ch]
  68. mov [esp+0Ch], eax
  69. mov eax, [esi+8]
  70. mov dword ptr [esp+4], offset a2ndElementDD ; "2nd element: %d %d\n"
  71. mov dword ptr [esp], 1
  72. mov [esp+8], eax
  73. call __printf_chk
  74. mov esi, [esi] ; operator++: get ->next pointer
  75. mov eax, [esi+0Ch]
  76. mov [esp+0Ch], eax
  77. mov eax, [esi+8]
  78. mov dword ptr [esp+4], offset a3rdElementDD ; "3rd element: %d %d\n"
  79. mov dword ptr [esp], 1
  80. mov [esp+8], eax
  81. call __printf_chk
  82. mov eax, [esi] ; operator++: get ->next pointer
  83. mov edx, [eax+0Ch]
  84. mov [esp+0Ch], edx
  85. mov eax, [eax+8]
  86. mov dword ptr [esp+4], offset aElementAt_endD ; "element at .end(): %d %d\n"
  87. mov dword ptr [esp], 1
  88. mov [esp+8], eax
  89. call __printf_chk
  90. mov dword ptr [esp], offset aLetSCountFro_0 ; "* let’s count from the end:"
  91. call puts
  92. mov eax, [esp+1Ch]
  93. mov dword ptr [esp+4], offset aElementAt_endD ; "element at .end(): %d %d\n"
  94. mov dword ptr [esp], 1
  95. mov [esp+0Ch], eax
  96. mov eax, [esp+18h]
  97. mov [esp+8], eax
  98. call __printf_chk
  99. mov esi, [esp+14h]
  100. mov eax, [esi+0Ch]
  101. mov [esp+0Ch], eax
  102. mov eax, [esi+8]
  103. mov dword ptr [esp+4], offset a3rdElementDD ; "3rd element: %d %d\n"
  104. mov dword ptr [esp], 1
  105. mov [esp+8], eax
  106. call __printf_chk
  107. mov esi, [esi+4] ; operator--: get ->prev pointer
  108. mov eax, [esi+0Ch]
  109. mov [esp+0Ch], eax
  110. mov eax, [esi+8]
  111. mov dword ptr [esp+4], offset a2ndElementDD ; "2nd element: %d %d\n"
  112. mov dword ptr [esp], 1
  113. mov [esp+8], eax
  114. call __printf_chk
  115. mov eax, [esi+4] ; operator--: get ->prev pointer
  116. mov edx, [eax+0Ch]
  117. mov [esp+0Ch], edx
  118. mov eax, [eax+8]
  119. mov dword ptr [esp+4], offset a1stElementDD ; "1st element: %d %d\n"
  120. mov dword ptr [esp], 1
  121. mov [esp+8], eax
  122. call __printf_chk
  123. mov dword ptr [esp], offset aRemovingLastEl ; "removing last element..."
  124. call puts
  125. mov esi, [esp+14h]
  126. mov [esp], esi
  127. call _ZNSt8__detail15_List_node_base9_M_unhookEv ; std::__detail::
  128. _List_node_base::_M_unhook(void)
  129. mov [esp], esi ; void *
  130. call _ZdlPv ; operator delete(void *)
  131. mov [esp], ebx
  132. call _Z13dump_List_valPj ; dump_List_val(uint *)
  133. mov [esp], ebx
  134. call _ZNSt10_List_baseI1aSaIS0_EE8_M_clearEv ; std::_List_base<a,std::
  135. allocator<a>>::_M_clear(void)
  136. lea esp, [ebp-8]
  137. xor eax, eax
  138. pop ebx
  139. pop esi
  140. pop ebp
  141. retn
  142. main endp

Listing 34.8: 所有输出

  1. * empty list:
  2. ptr=0x0028fe90 _Next=0x0028fe90 _Prev=0x0028fe90 x=3 y=0
  3. * 3-elements list:
  4. ptr=0x000349a0 _Next=0x00034988 _Prev=0x0028fe90 x=3 y=4
  5. ptr=0x00034988 _Next=0x00034b40 _Prev=0x000349a0 x=1 y=2
  6. ptr=0x00034b40 _Next=0x0028fe90 _Prev=0x00034988 x=5 y=6
  7. ptr=0x0028fe90 _Next=0x000349a0 _Prev=0x00034b40 x=5 y=6
  8. node at .begin:
  9. ptr=0x000349a0 _Next=0x00034988 _Prev=0x0028fe90 x=3 y=4
  10. node at .end:
  11. ptr=0x0028fe90 _Next=0x000349a0 _Prev=0x00034b40 x=5 y=6
  12. * lets count from the begin:
  13. 1st element: 3 4
  14. 2nd element: 1 2
  15. 3rd element: 5 6
  16. element at .end(): 5 6
  17. * lets count from the end:
  18. element at .end(): 5 6
  19. 3rd element: 5 6
  20. 2nd element: 1 2
  21. 1st element: 3 4
  22. removing last element...
  23. ptr=0x000349a0 _Next=0x00034988 _Prev=0x0028fe90 x=3 y=4
  24. ptr=0x00034988 _Next=0x0028fe90 _Prev=0x000349a0 x=1 y=2
  25. ptr=0x0028fe90 _Next=0x000349a0 _Prev=0x00034988 x=5 y=6

MSVC

MSVC实现形式基本相同,但是它存储了当前list的大小。这意味着.size()方法非常迅速,只需要从内存中读取一个值即可。从另一个方面来说,必须在插入和删除操作后更新size变量的值。 MSVC组织节点的方式也稍有不同。

GCC将垃圾元素放在链表的尾部,而MSVC放在链表起始位置。

Listing 34.9: MSVC 2012 /Fa2.asm /Ox /GS- /Ob1

  1. _l$ = -16 ; size = 8
  2. _t1$ = -8 ; size = 8
  3. _main PROC
  4. sub esp, 16 ; 00000010H
  5. push ebx
  6. push esi
  7. push edi
  8. push 0
  9. push 0
  10. lea ecx, DWORD PTR _l$[esp+36]
  11. mov DWORD PTR _l$[esp+40], 0
  12. ; allocate first "garbage" element
  13. call ?_Buynode0@?$_List_alloc@$0A@U?$_List_base_types@Ua@@V?
  14. $allocator@Ua@@@std@@@std@@@std@@QAEPAU?$_List_node@Ua@@PAX@2@PAU32@0@Z ; std::_List_alloc<0,
  15. std::_List_base_types<a,std::allocator<a> > >::_Buynode0
  16. mov edi, DWORD PTR __imp__printf
  17. mov ebx, eax
  18. push OFFSET $SG40685 ; ’* empty list:’
  19. mov DWORD PTR _l$[esp+32], ebx
  20. call edi ; printf
  21. lea eax, DWORD PTR _l$[esp+32]
  22. push eax
  23. call ?dump_List_val@@YAXPAI@Z ; dump_List_val
  24. mov esi, DWORD PTR [ebx]
  25. add esp, 8
  26. lea eax, DWORD PTR _t1$[esp+28]
  27. push eax
  28. push DWORD PTR [esi+4]
  29. lea ecx, DWORD PTR _l$[esp+36]
  30. push esi
  31. mov DWORD PTR _t1$[esp+40], 1 ; data for a new node
  32. mov DWORD PTR _t1$[esp+44], 2 ; data for a new node
  33. ; allocate new node
  34. call ??$_Buynode@ABUa@@@?$_List_buy@Ua@@V?$allocator@Ua@@@std@@@std@@QAEPAU?
  35. $_List_node@Ua@@PAX@1@PAU21@0ABUa@@@Z ; std::_List_buy<a,std::allocator<a> >::_Buynode<a
  36. const &>
  37. mov DWORD PTR [esi+4], eax
  38. mov ecx, DWORD PTR [eax+4]
  39. mov DWORD PTR _t1$[esp+28], 3 ; data for a new node
  40. mov DWORD PTR [ecx], eax
  41. mov esi, DWORD PTR [ebx]
  42. lea eax, DWORD PTR _t1$[esp+28]
  43. push eax
  44. push DWORD PTR [esi+4]
  45. lea ecx, DWORD PTR _l$[esp+36]
  46. push esi
  47. mov DWORD PTR _t1$[esp+44], 4 ; data for a new node
  48. ; allocate new node
  49. call ??$_Buynode@ABUa@@@?$_List_buy@Ua@@V?$allocator@Ua@@@std@@@std@@QAEPAU?
  50. $_List_node@Ua@@PAX@1@PAU21@0ABUa@@@Z ; std::_List_buy<a,std::allocator<a> >::_Buynode<a
  51. const &>
  52. mov DWORD PTR [esi+4], eax
  53. mov ecx, DWORD PTR [eax+4]
  54. mov DWORD PTR _t1$[esp+28], 5 ; data for a new node
  55. mov DWORD PTR [ecx], eax
  56. lea eax, DWORD PTR _t1$[esp+28]
  57. push eax
  58. push DWORD PTR [ebx+4]
  59. lea ecx, DWORD PTR _l$[esp+36]
  60. push ebx
  61. mov DWORD PTR _t1$[esp+44], 6 ; data for a new node
  62. ; allocate new node
  63. call ??$_Buynode@ABUa@@@?$_List_buy@Ua@@V?$allocator@Ua@@@std@@@std@@QAEPAU?
  64. $_List_node@Ua@@PAX@1@PAU21@0ABUa@@@Z ; std::_List_buy<a,std::allocator<a> >::_Buynode<a
  65. const &>
  66. mov DWORD PTR [ebx+4], eax
  67. mov ecx, DWORD PTR [eax+4]
  68. push OFFSET $SG40689 ; ’* 3-elements list:’
  69. mov DWORD PTR _l$[esp+36], 3
  70. mov DWORD PTR [ecx], eax
  71. call edi ; printf
  72. lea eax, DWORD PTR _l$[esp+32]
  73. push eax
  74. call ?dump_List_val@@YAXPAI@Z ; dump_List_val
  75. push OFFSET $SG40831 ; node at .begin:’
  76. call edi ; printf
  77. push DWORD PTR [ebx] ; get next field of node $l$ variable points to
  78. call ?dump_List_node@@YAXPAUList_node@@@Z ; dump_List_node
  79. push OFFSET $SG40835 ; node at .end:’
  80. call edi ; printf
  81. push ebx ; pointer to the node $l$ variable points to!
  82. call ?dump_List_node@@YAXPAUList_node@@@Z ; dump_List_node
  83. push OFFSET $SG40839 ; ’* let’’s count from the begin:’
  84. call edi ; printf
  85. mov esi, DWORD PTR [ebx] ; operator++: get ->next pointer
  86. push DWORD PTR [esi+12]
  87. push DWORD PTR [esi+8]
  88. push OFFSET $SG40846 ; 1st element: %d %d
  89. call edi ; printf
  90. mov esi, DWORD PTR [esi] ; operator++: get ->next pointer
  91. push DWORD PTR [esi+12]
  92. push DWORD PTR [esi+8]
  93. push OFFSET $SG40848 ; 2nd element: %d %d
  94. call edi ; printf
  95. mov esi, DWORD PTR [esi] ; operator++: get ->next pointer
  96. push DWORD PTR [esi+12]
  97. push DWORD PTR [esi+8]
  98. push OFFSET $SG40850 ; 3rd element: %d %d
  99. call edi ; printf
  100. mov eax, DWORD PTR [esi] ; operator++: get ->next pointer
  101. add esp, 64 ; 00000040H
  102. push DWORD PTR [eax+12]
  103. push DWORD PTR [eax+8]
  104. push OFFSET $SG40852 ; element at .end(): %d %d
  105. call edi ; printf
  106. push OFFSET $SG40853 ; ’* let’’s count from the end:’
  107. call edi ; printf
  108. push DWORD PTR [ebx+12] ; use x and y fields from the node $l$ variable points to
  109. push DWORD PTR [ebx+8]
  110. push OFFSET $SG40860 ; element at .end(): %d %d
  111. call edi ; printf
  112. mov esi, DWORD PTR [ebx+4] ; operator--: get ->prev pointer
  113. push DWORD PTR [esi+12]
  114. push DWORD PTR [esi+8]
  115. push OFFSET $SG40862 ; 3rd element: %d %d
  116. call edi ; printf
  117. mov esi, DWORD PTR [esi+4] ; operator--: get ->prev pointer
  118. push DWORD PTR [esi+12]
  119. push DWORD PTR [esi+8]
  120. push OFFSET $SG40864 ; 2nd element: %d %d
  121. call edi ; printf
  122. mov eax, DWORD PTR [esi+4] ; operator--: get ->prev pointer
  123. push DWORD PTR [eax+12]
  124. push DWORD PTR [eax+8]
  125. push OFFSET $SG40866 ; 1st element: %d %d
  126. call edi ; printf
  127. add esp, 64 ; 00000040H
  128. push OFFSET $SG40867 ; removing last element...’
  129. call edi ; printf
  130. mov edx, DWORD PTR [ebx+4]
  131. add esp, 4
  132. ; prev=next?
  133. ; it is the only element, "garbage one"?
  134. ; if yes, do not delete it!
  135. cmp edx, ebx
  136. je SHORT $LN349@main
  137. mov ecx, DWORD PTR [edx+4]
  138. mov eax, DWORD PTR [edx]
  139. mov DWORD PTR [ecx], eax
  140. mov ecx, DWORD PTR [edx]
  141. mov eax, DWORD PTR [edx+4]
  142. push edx
  143. mov DWORD PTR [ecx+4], eax
  144. call ??3@YAXPAX@Z ; operator delete
  145. add esp, 4
  146. mov DWORD PTR _l$[esp+32], 2
  147. $LN349@main:
  148. lea eax, DWORD PTR _l$[esp+28]
  149. push eax
  150. call ?dump_List_val@@YAXPAI@Z ; dump_List_val
  151. mov eax, DWORD PTR [ebx]
  152. add esp, 4
  153. mov DWORD PTR [ebx], ebx
  154. mov DWORD PTR [ebx+4], ebx
  155. cmp eax, ebx
  156. je SHORT $LN412@main
  157. $LL414@main:
  158. mov esi, DWORD PTR [eax]
  159. push eax
  160. call ??3@YAXPAX@Z ; operator delete
  161. add esp, 4
  162. mov eax, esi
  163. cmp esi, ebx
  164. jne SHORT $LL414@main
  165. $LN412@main:
  166. push ebx
  167. call ??3@YAXPAX@Z ; operator delete
  168. add esp, 4
  169. xor eax, eax
  170. pop edi
  171. pop esi
  172. pop ebx
  173. add esp, 16 ; 00000010H
  174. ret 0
  175. _main ENDP

与GCC不同,MSVC代码在函数开始,通过Buynode函数分配垃圾元素,这个方法也用于后续节点的申请(GCC将最早的节点分配在栈上)。

Listing 34.10 完整输出

  1. * empty list:
  2. _Myhead=0x003CC258, _Mysize=0
  3. ptr=0x003CC258 _Next=0x003CC258 _Prev=0x003CC258 x=6226002 y=4522072
  4. * 3-elements list:
  5. _Myhead=0x003CC258, _Mysize=3
  6. ptr=0x003CC258 _Next=0x003CC288 _Prev=0x003CC2A0 x=6226002 y=4522072
  7. ptr=0x003CC288 _Next=0x003CC270 _Prev=0x003CC258 x=3 y=4
  8. ptr=0x003CC270 _Next=0x003CC2A0 _Prev=0x003CC288 x=1 y=2
  9. ptr=0x003CC2A0 _Next=0x003CC258 _Prev=0x003CC270 x=5 y=6
  10. node at .begin:
  11. ptr=0x003CC288 _Next=0x003CC270 _Prev=0x003CC258 x=3 y=4
  12. node at .end:
  13. ptr=0x003CC258 _Next=0x003CC288 _Prev=0x003CC2A0 x=6226002 y=4522072
  14. * lets count from the begin:
  15. 1st element: 3 4
  16. 2nd element: 1 2
  17. 3rd element: 5 6
  18. element at .end(): 6226002 4522072
  19. * lets count from the end:
  20. element at .end(): 6226002 4522072
  21. 3rd element: 5 6
  22. 2nd element: 1 2
  23. 1st element: 3 4
  24. removing last element...
  25. _Myhead=0x003CC258, _Mysize=2
  26. ptr=0x003CC258 _Next=0x003CC288 _Prev=0x003CC270 x=6226002 y=4522072
  27. ptr=0x003CC288 _Next=0x003CC270 _Prev=0x003CC258 x=3 y=4
  28. ptr=0x003CC270 _Next=0x003CC258 _Prev=0x003CC288 x=1 y=2