2013年12月28日 星期六

提供自己的 operator new/delete

the 1st edition: 20131228
the 2nd edition: 20160219
the 3rd edition: 20180919

這篇不是在說明 overloaded operator new, 這件事情所有的 c++ 入門書籍都會告知你, 若你看的那本沒有, 可以考慮換掉它。

本範例一樣是作業系統等級的程式碼 (bare-metal), 在完成了 global/static object 的研究之後, 來看看怎麼實作 new/delete, 在 c++ 若不能使用 new/delete, 好像說不過去。

測試方式:
在 linux compile 成 dos .com 檔, 然後放到 qemu/dos 上執行。

no_impl_new_delete.cpp
 1 __asm__(".code16gcc\n");
 2 #include "io.h"
 3 #include "obj.h"
 4 
 5 typedef signed char s8;
 6 typedef signed short s16;
 7 typedef signed int s32;
 8 
 9 typedef unsigned char u8;
10 typedef unsigned short u16;
11 typedef unsigned int u32;
12 
13 #define BOCHS_MB __asm__ __volatile__("xchg %bx, %bx");
14 
15 extern int _start_ctors;
16 extern int _end_ctors;
17 
18 void s16_print_int(int i, int radix);
19 void print_str(const char   *s);
20 
21 extern int obj_count;
22 
23 
24 const int SOME_BASE_ADDRESS = 0x500;
25 
26 void myfree(void *ptr)
27 {
28   print_str("myfree\r\n");
29 }
30 
31 void *mymalloc(int size)
32 {
33   print_str("size: ");
34   s16_print_int(size, 10);
35   print_str("\r\n");
36   static char mem[256];
37   return mem;
38 }
39 

52 
53 extern "C" int cpp_main(void)
54 {
55   print_str("cpp_main\r\n");
56   s16_print_int(obj_count, 10);
57   print_str("\r\n");
58   
59   Io *io_p = new Io;
60   const char *ver=__VERSION__;
61   io_p->print("io_p g++ version: ");
62   io_p->print(ver);
63   io_p->print("\r\n");
64   delete io_p;
65 
66   return 0;
67 }

L59 和我們平常使用的 c++ new 用法一模一樣, new 會做類似 malloc 的動作, 先去要一塊記憶體, 並呼叫 ctor, 可是在作業系統之前的程式, 根本沒有記憶體管理的程式碼 (記得我們沒有 c++ runtime 來幫我們做這件事情嗎?), 來看看編譯會怎麼樣?

descent@debian-vm:dos_cpp$ make new_test.bin
ld -m elf_i386 -static -Tcpp.ld -nostdlib -M -o new_test.elf cpp_init.o new_test.o io.o cpp_abi.o obj.o dos_io.o > new_test.elf.map
new_test.o: In function `cpp_main':
/home/descent/git/simple_os/cpp_runtime/global_object/dos_cpp/new_test.cpp:59: undefined reference to `operator new(unsigned int)'
/home/descent/git/simple_os/cpp_runtime/global_object/dos_cpp/new_test.cpp:64: undefined reference to `operator delete(void*)'
make: *** [new_test.elf] Error 1

呵呵, 果然有預期的錯誤, 要是沒錯誤反而令我害怕。訊息很容易理解, 沒定義 operator new, operator delete, 所以該怎麼辦? 不難, 自己定義就好了。

new_test.cpp L76, L81 提供了 operator new, operator delete, 比想像中還要簡單吧! 這時候再編譯就沒問題了。有問題的是那個 mymalloc 要怎麼寫是吧?

這個的確是個問題, 得先搞定記憶體管理才能把這部份寫好, 一開始也困擾我許久, 因為我這部份還沒做得很好 (有趣吧! 我的 simple os 竟然沒有記憶體管理機制), 不過我只想搞定 c++ runtime new/delete, 並不是要實作記憶體管理機制 (這個就不容易了), 所以想了個簡單的辦法來模擬 malloc 的行為。

L26, L31 的 mymalloc/myfree 應該很簡單, 簡單到不用說明吧?

new_test.cpp
  1 __asm__(".code16gcc\n");
  2 #include "io.h"
  3 #include "obj.h"
  4 
  5 typedef signed char s8;
  6 typedef signed short s16;
  7 typedef signed int s32;
  8 
  9 typedef unsigned char u8;
 10 typedef unsigned short u16;
 11 typedef unsigned int u32;
 12 
 13 
 14 #define BOCHS_MB __asm__ __volatile__("xchg %bx, %bx");
 15 
 16 #if 0
 17 Ab ab(7);
 18 Io io;
 19 Obj1 g_obj1;
 20 #endif
 21 
 22 extern int _start_ctors;
 23 extern int _end_ctors;
 24 
 25 void s16_print_int(int i, int radix);
 26 void print_str(const char   *s);
 27 
 28 extern int obj_count;
 29 
 30 u16 asm_get_ds(void)
 31 {
 32   u16 v=0;
 33   __asm__ __volatile__
 34     (
 35       "mov %%ds, %%ax\n"
 36       : "=a"(v)// output
 37     );
 38   return v;
 39 }
 40 
 41 u16 asm_get_cs(void)
 42 {
 43   u16 v=0;
 44   __asm__ __volatile__
 45     (
 46       "mov %%cs, %%ax\n"
 47       : "=a"(v)// output
 48     );
 49   return v;
 50 }
 51 
 52 const int SOME_BASE_ADDRESS = 0x500;
 53 
 54 void myfree(void *ptr)
 55 {
 56   print_str("myfree\r\n");
 57 }
 58 
 59 void *mymalloc(int size)
 60 {
 61   print_str("mymalloc ## size: ");
 62   s16_print_int(size, 10);
 63   print_str("\r\n");
 64   static char mem[256];
 65   return mem;
 66 #if 0
 67   // ref: http://wiki.osdev.org/C%2B%2B_Exception_Support
 68   static char* freeMemoryBase = reinterpret_cast<char *>(SOME_BASE_ADDRESS);
 69   size = (size + 7) / 8 * 8;
 70   freeMemoryBase += size;
 71   return freeMemoryBase - size;
 72 #endif
 73 }
 74 
 75 // ref: http://wiki.osdev.org/C%2B%2B#The_Operators_.27new.27_and_.27delete.27
 76 void *operator new(unsigned int s)
 77 {
 78   return mymalloc(s);
 79 }
 80 
 81 void operator delete(void *p)
 82 {
 83   myfree(p);
 84 }
 85 
 86 // new/delete: array version
 87 
 88 void *operator new[] (unsigned int s)
 89 {
 90   return mymalloc(s);
 91 }
 92 
 93 void operator delete[] (void *p)
 94 {
 95   myfree(p);
 96 }
 97 
 98 // new/delete: no exception version
 99 
100 
101 struct nothrow_t { };
102 
103 const nothrow_t nothrow;
104 
105 void *operator new (unsigned int s, const nothrow_t&) 
106 {
107   return mymalloc(s);
108 }
109 
110 
111 extern "C" int cpp_main(void)
112 {
113   print_str("cpp_main\r\n");
114   s16_print_int(obj_count, 10);
115   print_str("\r\n");
116   
117   Io *io_p = new Io;
118   const char *ver=__VERSION__;
119   io_p->print("io_p g++ version: ");
120   io_p->print(ver);
121   io_p->print("\r\n");
122   delete io_p;
123 
124   // test new array
125   int *pia = new int[1024];
126   delete [] pia;
127 
128   // test no exception new
129 
130   Io *ne_io_p =  new(nothrow) Io;
131 
153   return 0;
154 }

new_test.cpp L117 new 發動時, L76 operator new 會被 c++ 編譯器傳入正確的 class size, 在 Io class 這個例子, size 是 4, list 1 便是在 dos 下的執行結果 (qemu)。可以看到除了呼叫配置記憶體的程式碼之外, new 還會「順便」呼叫 constructor, 這才是 c++ 的 new 和 malloc 不一樣的地方, c++ compiler 會知道要喚起那些 class 的 constructor, 很神奇吧! c++ compiler 難寫是有道理的。

你寫了一行的 new, 但 c++ compiler 卻產生兩行 code, c++ 的黑箱作業可不是說假的。

list 1. qemu-system-i386 -fda dos622.img -nographic (使用 -nographic 需要 qemu 2.12 以上的版本)
 1 iPXE (http://ipxe.org) 00:03.0 C980 PCI2.10 PnP PMM+07F92340+07EF2340 C980
 2                                                                                
 3 
 4 
 5 Booting from Hard Disk...
 6 Boot failed: could not read the boot disk
 7 
 8 Booting from Floppy...
 9 Starting MS-DOS...
10 
11 A:\>new_test
12 cpp_main
13 0
14 mymalloc ## size: 4
15 Io ctor: data member
16 io_p g++ version: 7.3.0
17 io dtor
18 myfree
19 mymalloc ## size: 4096
20 myfree
21 mymalloc ## size: 4
22 Io ctor: data member
23 g_dtors
24 0

比起 global/static object, operator new/delete 簡單到令我驚訝 (當然, 真的要處理記憶體的分配與回收就沒那麼簡單了)。所以現在你知道在作業系統之下寫 c++ 程式, c++ runtime library 的辛勞了。

source code:
https://github.com/descent/simple_os
commit 93e327234d153636e315721ca66f73df0e7c0887
cpp_runtime branch

這是 g++ 裡頭的實作, 不難懂。


gcc-4.9.2/libstdc++-v3/libsupc++/new_op.cc

 1 // Support routines for the -*- C++ -*- dynamic memory management.
 2 
 3 // Copyright (C) 1997-2014 Free Software Foundation, Inc.
 4 //
 5 // This file is part of GCC.
 6 //
 7 // GCC is free software; you can redistribute it and/or modify
 8 // it under the terms of the GNU General Public License as published by
 9 // the Free Software Foundation; either version 3, or (at your option)
10 // any later version.
11 //
12 // GCC is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 // GNU General Public License for more details.
16 //
17 // Under Section 7 of GPL version 3, you are granted additional
18 // permissions described in the GCC Runtime Library Exception, version
19 // 3.1, as published by the Free Software Foundation.
20 
21 // You should have received a copy of the GNU General Public License and
22 // a copy of the GCC Runtime Library Exception along with this program;
23 // see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
24 // <http://www.gnu.org/licenses/>.
25 
26 #include <bits/c++config.h>
27 #include <cstdlib>
28 #include <bits/exception_defines.h>
29 #include "new"
30 
31 using std::new_handler;
32 using std::bad_alloc;
33 #if _GLIBCXX_HOSTED
34 using std::malloc;
35 #else
36 // A freestanding C runtime may not provide "malloc" -- but there is no
37 // other reasonable way to implement "operator new".
38 extern "C" void *malloc (std::size_t);
39 #endif
40 
41 _GLIBCXX_WEAK_DEFINITION void *
42 operator new (std::size_t sz) _GLIBCXX_THROW (std::bad_alloc)
43 {
44   void *p;
45 
46   /* malloc (0) is unpredictable; avoid it.  */
47   if (sz == 0)
48     sz = 1;
49   p = (void *) malloc (sz);
50   while (p == 0)
51     {
52       new_handler handler = std::get_new_handler ();
53       if (! handler)
54  _GLIBCXX_THROW_OR_ABORT(bad_alloc());
55       handler ();
56       p = (void *) malloc (sz);
57     }
58 
59   return p;
60 }

除了最基本的 new, 還有另外一個版本, 不拋出 exception 的版本。這個 operator new 多了一個 const std::nothrow_t& 參數。

gcc-5.5.0/libstdc++-v3/libsupc++/new_opnt.cc
 1 // Support routines for the -*- C++ -*- dynamic memory management.
 2 // Copyright (C) 1997-2015 Free Software Foundation, Inc.
 3 //
 4 // This file is part of GCC.
 5 //
 6 // GCC is free software; you can redistribute it and/or modify
 7 // it under the terms of the GNU General Public License as published by
 8 // the Free Software Foundation; either version 3, or (at your option)
 9 // any later version.
10 //
11 // GCC is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 // GNU General Public License for more details.
15 //
16 // Under Section 7 of GPL version 3, you are granted additional
17 // permissions described in the GCC Runtime Library Exception, version
18 // 3.1, as published by the Free Software Foundation.
19 
20 // You should have received a copy of the GNU General Public License and
21 // a copy of the GCC Runtime Library Exception along with this program;
22 // see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
23 // <http://www.gnu.org/licenses/>.
24 
25 #include <bits/c++config.h>
26 #include <bits/exception_defines.h>
27 #include "new"
28 
29 using std::new_handler;
30 using std::bad_alloc;
31 
32 extern "C" void *malloc (std::size_t);
33 
34 _GLIBCXX_WEAK_DEFINITION void *
35 operator new (std::size_t sz, const std::nothrow_t&) _GLIBCXX_USE_NOEXCEPT
36 {
37   void *p;
38 
39   /* malloc (0) is unpredictable; avoid it.  */
40   if (sz == 0)
41     sz = 1;
42 
43   while (__builtin_expect ((p = malloc (sz)) == 0, false))
44     {
45       new_handler handler = std::get_new_handler ();
46       if (! handler)
47  return 0;
48       __try
49  {
50    handler ();
51  }
52       __catch(const bad_alloc&)
53  {
54    return 0;
55  }
56     }
57 
58   return p;
59 }

gcc-5.5.0/libstdc++-v3/libsupc++/new
  1 // The -*- C++ -*- dynamic memory management header.
  2
  3 // Copyright (C) 1994-2015 Free Software Foundation, Inc.
  4
  5 // This file is part of GCC.
  6 //
  7 // GCC is free software; you can redistribute it and/or modify
  8 // it under the terms of the GNU General Public License as published by
  9 // the Free Software Foundation; either version 3, or (at your option)
 10 // any later version.
 11 //
 12 // GCC is distributed in the hope that it will be useful,
 13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
 14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 15 // GNU General Public License for more details.
 16 //
 17 // Under Section 7 of GPL version 3, you are granted additional
 18 // permissions described in the GCC Runtime Library Exception, version
 19 // 3.1, as published by the Free Software Foundation.
 20
 21 // You should have received a copy of the GNU General Public License and
 22 // a copy of the GCC Runtime Library Exception along with this program;
 23 // see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
 24 // <http://www.gnu.org/licenses/>.
 25
 26 /** @file new
 27  *  This is a Standard C++ Library header.
 28  *
 29  *  The header @c new defines several functions to manage dynamic memory and
 30  *  handling memory allocation errors; see
 31  *  http://gcc.gnu.org/onlinedocs/libstdc++/18_support/howto.html#4 for more.
 32  */
 33
 34 #ifndef _NEW
 35 #define _NEW
 36
 37 #pragma GCC system_header
 38
 39 #include <bits/c++config.h>
 40 #include <exception>
 41
 42 #pragma GCC visibility push(default)
 43
 44 extern "C++" {
 45
 46 namespace std
 47 {
 48   /**
 49    *  @brief  Exception possibly thrown by @c new.
 50    *  @ingroup exceptions
 51    *
 52    *  @c bad_alloc (or classes derived from it) is used to report allocation
 53    *  errors from the throwing forms of @c new.  */
 54   class bad_alloc : public exception
 55   {
 56   public:
 57     bad_alloc() throw() { }
 58
 59     // This declaration is not useless:
 60     // http://gcc.gnu.org/onlinedocs/gcc-3.0.2/gcc_6.html#SEC118
 61     virtual ~bad_alloc() throw();
 62
 63     // See comment in eh_exception.cc.
 64     virtual const char* what() const throw();
 65   };
 66
 67 #if __cplusplus >= 201103L
 68   class bad_array_new_length : public bad_alloc
 69   {
 70   public:
 71     bad_array_new_length() throw() { };
 72
 73     // This declaration is not useless:
 74     // http://gcc.gnu.org/onlinedocs/gcc-3.0.2/gcc_6.html#SEC118
 75     virtual ~bad_array_new_length() throw();
 76
 77     // See comment in eh_exception.cc.
 78     virtual const char* what() const throw();
 79   };
 80 #endif
 81
 82   struct nothrow_t { };
 83
 84   extern const nothrow_t nothrow;
 85
 86   /** If you write your own error handler to be called by @c new, it must
 87    *  be of this type.  */
 88   typedef void (*new_handler)();
 89
 90   /// Takes a replacement handler as the argument, returns the
 91   /// previous handler.
 92   new_handler set_new_handler(new_handler) throw();
 93
 94 #if __cplusplus >= 201103L
 95   /// Return the current new handler.
 96   new_handler get_new_handler() noexcept;
 97 #endif
 98 } // namespace std
 99
100 //@{
101 /** These are replaceable signatures:
102  *  - normal single new and delete (no arguments, throw @c bad_alloc on error)
103  *  - normal array new and delete (same)
104  *  - @c nothrow single new and delete (take a @c nothrow argument, return
105  *    @c NULL on error)
106  *  - @c nothrow array new and delete (same)
107  *
108  *  Placement new and delete signatures (take a memory address argument,
109  *  does nothing) may not be replaced by a user's program.
110 */
111 void* operator new(std::size_t) _GLIBCXX_THROW (std::bad_alloc)
112   __attribute__((__externally_visible__));
113 void* operator new[](std::size_t) _GLIBCXX_THROW (std::bad_alloc)
114   __attribute__((__externally_visible__));
115 void operator delete(void*) _GLIBCXX_USE_NOEXCEPT
116   __attribute__((__externally_visible__));
117 void operator delete[](void*) _GLIBCXX_USE_NOEXCEPT
118   __attribute__((__externally_visible__));
119 void* operator new(std::size_t, const std::nothrow_t&) _GLIBCXX_USE_NOEXCEPT
120   __attribute__((__externally_visible__));
121 void* operator new[](std::size_t, const std::nothrow_t&) _GLIBCXX_USE_NOEXCEPT
122   __attribute__((__externally_visible__));
123 void operator delete(void*, const std::nothrow_t&) _GLIBCXX_USE_NOEXCEPT
124   __attribute__((__externally_visible__));
125 void operator delete[](void*, const std::nothrow_t&) _GLIBCXX_USE_NOEXCEPT
126   __attribute__((__externally_visible__));
127
128 // Default placement versions of operator new.
129 inline void* operator new(std::size_t, void* __p) _GLIBCXX_USE_NOEXCEPT
130 { return __p; }
131 inline void* operator new[](std::size_t, void* __p) _GLIBCXX_USE_NOEXCEPT
132 { return __p; }
133
134 // Default placement versions of operator delete.
135 inline void operator delete  (void*, void*) _GLIBCXX_USE_NOEXCEPT { }
136 inline void operator delete[](void*, void*) _GLIBCXX_USE_NOEXCEPT { }
137 //@}
138 } // extern "C++"
139
140 #pragma GCC visibility pop
141
142 #endif

再來還有一個 new array 的版本:
new_opv.cc
 1 // Boilerplate support routines for -*- C++ -*- dynamic memory management.
 2
 3 // Copyright (C) 1997-2015 Free Software Foundation, Inc.
 4 //
 5 // This file is part of GCC.
 6 //
 7 // GCC is free software; you can redistribute it and/or modify
 8 // it under the terms of the GNU General Public License as published by
 9 // the Free Software Foundation; either version 3, or (at your option)
10 // any later version.
11 //
12 // GCC is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 // GNU General Public License for more details.
16 //
17 // Under Section 7 of GPL version 3, you are granted additional
18 // permissions described in the GCC Runtime Library Exception, version
19 // 3.1, as published by the Free Software Foundation.
20
21 // You should have received a copy of the GNU General Public License and
22 // a copy of the GCC Runtime Library Exception along with this program;
23 // see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
24 // <http://www.gnu.org/licenses/>.
25
26 #include <bits/c++config.h>
27 #include "new"
28
29 _GLIBCXX_WEAK_DEFINITION void*
30 operator new[] (std::size_t sz) _GLIBCXX_THROW (std::bad_alloc)
31 {
32   return ::operator new(sz);
33 }

delete 當然也有一個對應的 array 版本:
del_opv.cc
 1 // Boilerplate support routines for -*- C++ -*- dynamic memory management.
 2 
 3 // Copyright (C) 1997-2015 Free Software Foundation, Inc.
 4 //
 5 // This file is part of GCC.
 6 //
 7 // GCC is free software; you can redistribute it and/or modify
 8 // it under the terms of the GNU General Public License as published by
 9 // the Free Software Foundation; either version 3, or (at your option)
10 // any later version.
11 //
12 // GCC is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 // GNU General Public License for more details.
16 //
17 // Under Section 7 of GPL version 3, you are granted additional
18 // permissions described in the GCC Runtime Library Exception, version
19 // 3.1, as published by the Free Software Foundation.
20 
21 // You should have received a copy of the GNU General Public License and
22 // a copy of the GCC Runtime Library Exception along with this program;
23 // see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
24 // <http://www.gnu.org/licenses/>.
25 
26 #include <bits/c++config.h>
27 #include "new"
28 
29 _GLIBCXX_WEAK_DEFINITION void
30 operator delete[] (void *ptr) _GLIBCXX_USE_NOEXCEPT
31 {
32   ::operator delete (ptr);
33 }

new_test.cpp L88, 93, 105 是補上這些版本。

ref:

沒有留言:

張貼留言

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。