跳到主要內容

Notes for C/C++ Programming

Notes for C/C++ Programming

Notes for C/C++ Programming

Off by One

Error Prone

#define LEN 128
char buf[LEN] = {0};

// -1 for prevent missing NULL terminator
// since strncpy doesn't preserve it for us.
strncpy(buf, something, sizeof(buf) - 1); 

// snprintf does preserve NULL terminator for us
// i.e. actual size for placing characters is sizeof(buf) - 1
// but it takes extra time to parsing format
snprintf(buf, sizeof(buf), something);

Bad things often happen when we mess with -1 or +1 to pointers point to continuous elements.

Absorber

#define LEN 128
char buf[LEN + 1] = {0};
//           ^^^^ Always preserve 1 byte
strncpy(buf, somethin, sizeof(buf));

Constant Correctness (C/C++)

Bad

void do_something(char *dest, char *src);

struct MyList {
  void do_something(std::string &name);
};

Reason: Will the name be modified by the lookup method? Will state of a MyList instance be changed after calling the method?

Good

void do_something(char *dest, char const* src);

struct MyList {
  void do_something(std::string const &name) const;
};

Be explicit!

For-Loop (C++)

C++98

vector<int> vec{1,2,3};
for(vector<int>::iterator i = vec.begin(); i != vec.end(); ++i) { /* ... */ }

Use Auto and Global Iterator Operating Functions

#include <iterator>
// ...
vector<int> vec{1,2,3};
for(auto i = begin(vec); i != end(vec); next(i)) { /* ... */}

Note Other than next, we also have prev.

Use Range-base For-loop

vector<int> vec{1, 2, 3}; // Uniform initializer
// i is a `copy` of an element in vec
for(auto i : vec) { /* .... */ }

// i is a `reference` to an element in vec
for(auto &i : vec) { /* .... */ } 

Heap Allocation/Free (C )

Check NULL After Allocation

char *p = (char*) malloc(n);
if (!p) {
  // error out
}

Initialize As Zero With calloc

struct S {
   char s[4];
   char const *p;
};
// Create and initialize with zeroed content
struct S *my_struct = (struct S*) calloc(4, sizeof(struct S));

// if my_struct is not NULL then following assertions hold
assert(my_struct[0].s[0] == 0);
assert(my_struct[0].p == 0);

Free NULL Doesn’t Hurt But Dereference It Does

void MyFree(void **pptr)
{
    if(!ptr) return;  // Undefined behavior per standard 
    free(*ptr);       // Safe if *ptr == NULL per standard
}

GCC/Clang Extension __cleanup__ Attribute

NOT Supported by MSVC (It has try-final and __leave)

// Sometimes called poor man's destructor
#define SCOPED_FREE(Function) __attribute__((__cleanup__(Function)))

void free_char_p(char **ptr) { if(ptr) { free(*ptr); *ptr = NULL; } }

if(...) {
    SCOPED_FREE(free_char_p) char *s = malloc(4);
    // Call free(x) automatically when exiting scope
}

Sometimes its convenient to define new type for it.

#define local_string_t \
    SCOPED_FREE(free_char_p) char

if(...) {
    local_string_t *s = ...,
                   *y = ...;
}

Be careful when you need to assign different address to the pointer. See below.

local_string_t *s = malloc (5);
s = malloc(6); // previous allocated memory is leaked

Heap Management (C++)

WRONG
Surprisingly but I saw it in production code.

int *ptr = new int;
if (!ptr) {
  // Never reach
}
// ...
delete ptr;

Use std::nothrow

int *ptr = new (std::nothrow) int;
if (!ptr) {
    // If `new` failed, handle it here
 }
 // ...
 delete ptr;

Use Try-Catch

int *ptr = std::nullptr;
try {
   ptr = new int;
} catch (std::bad_alloc) {
   // Handle allocation failure
}
delete ptr;

Use Smart Pointers

#include <memory>
// ...
std::unique_ptr<int> ptr;
try {
  ptr = std::make_unique<int>(5);
} catch (std::bad_alloc) {
  // Handle allocation failure
}
// Note here, we don't do `delete`.

Try to find out when to use std::shared_ptr and std::unique_ptr.

Wrap C Create/Release Interfaces (C++)

// C interfaces
Handle *create_handle();
void free_handle(Handle *ptr);

std::unique_ptr<Handle> hdl = make_unique<Handle>(create_handle(), &free_handle);
// the free_handle() will be called when hdl exits scope.

Containers (C++)

Feature Candidate
C++ Compatible C-array std::array<T>.
General/Prototyping std::vector<T>.
Better for insertion std::list<T>.
Fast lookup std::set<T> and std::map<T, U>.
Even faster lookup std::unordered_set<T> and std::unordered_map<T, U>.

Algorithms & Lambda (C++)

Find

#include <algorithm>
#include <vector>
// ...
std::vector<int> vec{1,3,4};
// Find the first even number
auto iter = std::find_if(vec.begin(), vec.end(), 
						[](auto const &val){ 
							return 0 == val % 2; 
						});
if (iter != vec.end()) {
  // found
} else {
  // not found
}

Sort

struct point
{
  int x, y;
};

std::vector<point> points = {
		{1, 2}, {3,4}, {1,5}, {2, 6}
	};
// sort by point.x in descending order
std::sort(points.begin(), points.end(), 
		[](int const &x, int const &y){
			return x.x > y.x;
		});

Remove

std::vector<int> vec{1,3,4, 8, 9, 11, 14};
auto predictor = [](auto const &val){ 
					return 0 == val % 2; 
				};
// Move all matched elements to tail portion of the vector.
auto new_end = std::remove_if(vec.begin(), vec.end(), predictor);
vec.erase(new_end, vec.end());

Error Handling (C/C++)

Hard to Read

if (ok_1) {
   if(ok_2) {
      if(ok_3) {
      } else {
      }
   } else {
   }
} else {
}

Less Nesting with goto

int ret = 0;
if(!ok_1) {
   ret = E_INVALID;
   goto ERROR_;
}
if (!ok_2) {
} 
// you got the idea ...
ERROR_:
  log_error(ret);
return ret;

Less Nesting with while

int ret=0; 
while (1) {
  if (!ok_1) {
    ret = E_INVALID;
    break;
  }
  if(!ok_2) {}
  // ...
  break;
}

Never Mix Exception and Error Code

NEVER

int never_do_so() {
  int ret = 0;
  throw std::runtime_error();
  return ret;
}

Pick one of exception or error code, and convert all underneath to the selected one. e.g.

Convert to Error Code

int func() {
  int ret = 0;
  try {
    this_may_throw_bad_alloc();
    this_can_throw_out_of_range();
    this_throw_custom_exception();
  } catch(std::bad_alloc) {
    ret = E_NOMEM;
  } catch(std::out_of_range) {
    ret = E_RANGE;
  } catch(...) {
    ret = E_UNKNOWN;
  }
  return ret;
}

and vice versa.

Handle bad_alloc

I’ve mentioned new can throw, and it is used in C++ standard library. Unless specified by ISO standard, many methods of standard library can throw. e.g.

// Default constructor usually won't throw
std::string s;
std::vector<int> v;

try {
   // Following expressions can throw
   std::vector<int> v2{1, 2, 3};
   std::vector<int> v_cp(v2);
   
   v.push_back(123); 
   v = v2;
   
   s += "abcd";
   s.insert(0, "abcd");
} catch (bad_alloc){

}

// Move or deletion won't throw
v.clear();
v.swap(*v2);
s.pop_back();
v.assign({4, 5, 6});

Written with StackEdit.

留言

這個網誌中的熱門文章

得利油漆色卡編碼方式

得利油漆色卡編碼方式 類似 Munsell 色彩系統 ,編碼方式為 HUE LRV/CHROMA 例如 10GY 61/449 ( 色卡 ) 編碼數值 描述 10GY hue ,色輪上從 Y(ellow) 到 G(reen) 區分為 0 ~ 99 ,數值越小越靠近 Y,越大越靠近 G 61 LRV (Light Reflectance Value) 塗料反射光源的比率,數值從 0% ~ 100% ,越高越亮,反之越暗,也可理解為明度 449 chroma 可理解為彩度,數值沒有上限,越高顏色純度 (濃度) 越高 取決於測量儀器,對應至 RGB 並不保證視覺感受相同。 參考資料: 色卡對照網站 e-paint.co.uk Written with StackEdit .

UTF8 與 Unicode 的轉換 (C++)

UTF8 與 Unicode 的轉換 (C++) 先釐清一下這兩者的性質 Unicode: 為世界上所有的文字系統制訂的標準,基本上就是給每個字(letter)一個編號 UTF-8: 為 unicode 的編號制定一個數位編碼方法 UTF-8 是一個長度介於 1~6 byte 的編碼,將 unicode 編號 (code point) 分為六個區間如下表 1 Bits First code point Last code point Bytes Byte 1 Byte 2 Byte 3 Byte 4 Byte 5 Byte 6 7 U+0000 U+007F 1 0xxxxxxx 11 U+0080 U+07FF 2 110xxxxx 10xxxxxx 16 U+0800 U+FFFF 3 1110xxxx 10xxxxxx 10xxxxxx 21 U+10000 U+1FFFFF 4 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 26 U+200000 U+3FFFFFF 5 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 31 U+4000000 U+7FFFFFFF 6 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 觀察上面的表應該可以發現 除了 7 bits 的區間外,第一個 byte 開頭連續 1 的個數就是長度,例如 110XXXXX 就是 2 byte 長,而 1110xxxx 就是 3 byte 除了第一個 byte 外,之後的 byte 前兩個 bit 一定是 10 開頭,這樣的好處在於確立了編碼的 self-synchronizeing,意即當編碼為多個 byte 時,任取一個 byte 無法正常解碼。 Note 第一點中的例外 (7 bits) 是為了與 ASCII 的相容性,而第二點會影響到 code point 至 UTF-8 的轉換。 為了與 UTF-16 的相容性,在 R

C++17 新功能 try_emplace

C++17 新功能 try_emplace 回顧 emplace 大家的好朋友 Standard Template Library (STL) 容器提供如 push_back , insert 等介面,讓我們塞東西進去; C++11 之後,新增了 emplace 系列的介面,如 std::vector::emplace_back , std::map::emplace 等,差異在於 emplace 是在容器內 in-place 直接建構新元素,而不像 push_back 在傳遞參數前建構,下面用實例來說明: struct Value { // ctor1 Value ( int size ) : array ( new char [ size ] ) , size ( size ) { printf ( "ctor1: %d\n" , size ) ; } // ctor2 Value ( const Value & v ) : array ( new char [ v . size ] ) , size ( v . size ) { printf ( "ctor2: %d\n" , size ) ; memcpy ( array . get ( ) , v . array . get ( ) , size ) ; } private : std :: unique_ptr < char [ ] > array ; int size = 0 ; } ; struct Value 定義了自訂建構子 (ctor1),以指定大小 size 配置陣列,複製建構子 (ctor2) 則會配置與來源相同大小及內容的陣列,為了方便觀察加了一些 printf 。當我們如下使用 std::vector::push_back 時 std :: vector < Value > v ; v . push_back ( Value ( 2048 ) ) ; 首先 Value 會先呼叫 ctor1,傳給 push_ba