動機
整理一些之前的問題
extern, static, global
extern 的意思是去外面找,去其他的程式中找在哪 static 的意思是只在這個範圍存活&可見,用這個方式來看file(global)、function、class(only for c++)的static變數,就會很一致了
下面的三個code就是demo extern的用處
extern之後,a就可以用了 但是對於有static的global變數,linker會找不到他
aaa.cpp
int a = 10;
int b = 100;
static int c = 1000;
void x(){a--;}
void y(){b--;}
aaa.hpp
extern int b;
void x();
void y();
bbb.cpp
#include "aaa.hpp"
#include <iostream>
int main() {
//std::cout << a; error
std::cout << b << '\n';
y();
std::cout << "after y: " << b << '\n';
b--;
std::cout << "after b--: " << b << '\n';
extern int a;
std::cout << a;
x();
std::cout << "after a: " << a << '\n';
a--;
std::cout << "after a--: " << a << '\n';
// extern static int c; // linker fails, cant find c
}
extern “C”
在C++中函數可以overload,所以C++的函數其實編譯出來後名字會被加料,但
如果要include一些用c寫好的函數就會出事,名字會對不上,所以要用extern "C"
用extern "C"
就是說這段不要加料,照C的方式來
use C lib in C++
/* fromc.h */
#ifdef __cplusplus
extern "C" int get10();
#else
int get10();
#endif
/* fromc.c */
int get10() {return 10;}
// useclib.cpp
#include <iostream>
#include "fromc.h"
int main() {
std::cout << get10() << '\n';
}
use C++ lib in C
// fromcpp.hpp
int get20_for_C();
// fromcpp.cpp
int get20() {return 20;}
extern "C" {
int get20_for_C() {return get20();}
}
/* usecpplib.c */
#include <stdio.h>
#include "fromcpp.hpp"
int main() {
printf("%d\n", get20_for_C());
}
static 為什麼放在.data
程式在記憶體中會有
- stack&heap a. stack: 放local var與函數的call stack b. heap: 動態allocate的東西
- bss: uninit的static
- data: 放 全域變數與常數
- text: 程式碼
data 與 stack&heap 的差別是?
data的資料只要程式還在跑,就不會消失 stack&heap的資料隨時都有可能消失
所以static 為什麼放在.data? 因為static隨時都要存活,所以不能放在stack&heap,那就只能放在data
about C++ class
friend
struct A {
void normal_method() {}
friend void friend_method(A& me) {}
};
int main() {
A a;
//原本的method call
a.normal_method();
//不想放前面的method call,當然不一定是只能傳自己,這就是函數可以多個
friend_method(a);
}
virtual
有標virtual就是會在runtime時依據實際上的type去找method執行,如果沒有就是依據變數的type
#include <iostream>
struct A {
void x() {std::cout << "x\n";}
virtual void y() {std::cout << "y\n";}
};
struct B : public A {
void x() {std::cout << "x2\n";}
virtual void y() {std::cout << "y2\n";}
};
int main() {
A* a = new B();
a->x(); // x
a->y(); // y2
((B*)a)->x(); // x2
delete a;
}
注意: constructor不能是virtual constructor本來就是從new時的type建立回去,所以不需要virtual 另一個說法是new物件時還沒有virtual的table,所以不能是virtual
但destructor需要,因為如果像上面的code去刪一個parent指標,時沒有virtual,會從parent的destructor去跑,這就尷尬了
object slicing
就是從child轉到parent會讓child的東西不見,這就是slicing 但是,因為cpp有copy,事情可能會搞砸
如果從child到parent是用copy的話,就不會根據原本的資料去找attribute與method,同時如果要轉回去也會出事(要記得用dynamic_casr轉啊)
class Base
{
protected:
int m_value{};
public:
Base(int value) : m_value{ value } {}
virtual const char* getName() const { return "Base"; }
int getValue() const { return m_value; }
};
class Derived: public Base
{
public:
Derived(int value) : Base{ value } {}
virtual const char* getName() const { return "Derived"; }
};
void printName(const Base base) // note: base passed by value, not reference
{
std::cout << "I am a " << base.getName() << '\n';
}
int main()
{
Derived derived{ 5 };
std::cout << "derived is a " << derived.getName() << " and has value " << derived.getValue() << '\n';
Base &ref{ derived }; // ok
std::cout << "ref is a " << ref.getName() << " and has value " << ref.getValue() << '\n';
Base *ptr{ &derived }; // ok
std::cout << "ptr is a " << ptr->getName() << " and has value " << ptr->getValue() << '\n';
Base base{ derived }; // be sliced
std::cout << "base is a " << base.getName() << " and has value " << base.getValue() << '\n';
printName(derived); // be sliced
return 0;
}
the rule of five
如果定義了自己的
- destructor
- copy constructor
- copy assignment
就要把其他的也一起定義完
不過因為C++11有move,所以要多兩個
- move constructor
- move assignment
class rule_of_five
{
char* cstring; // raw pointer used as a handle to a dynamically-allocated memory block
public:
rule_of_five(const char* s = "")
: cstring(nullptr)
{
if (s) {
std::size_t n = std::strlen(s) + 1;
cstring = new char[n]; // allocate
std::memcpy(cstring, s, n); // populate
}
}
~rule_of_five()
{
delete[] cstring; // deallocate
}
rule_of_five(const rule_of_five& other) // copy constructor
: rule_of_five(other.cstring)
{}
rule_of_five(rule_of_five&& other) noexcept // move constructor
: cstring(std::exchange(other.cstring, nullptr))
{}
rule_of_five& operator=(const rule_of_five& other) // copy assignment
{
return *this = rule_of_five(other);
}
rule_of_five& operator=(rule_of_five&& other) noexcept // move assignment
{
std::swap(cstring, other.cstring);
return *this;
}
// alternatively, replace both assignment operators with
// rule_of_five& operator=(rule_of_five other) noexcept
// {
// std::swap(cstring, other.cstring);
// return *this;
// }
};
OS related
Edge Trigger & Level Trigger
- Edge Trigger: 狀態變化時產生io事件
- Level Trigger: 滿足條件時產生io事件
memory
- memory hierarchy
- register
- cache memory
- main memory
- HDD
- Direct Memory Access, DMA
- 讓device可以直接操作記憶體,像是device把資料copy到mem去
- 會導致cache不一致!! (因為直接改mem,但cache不知道)
- 寫入mem時要通知cache做invalidate
- 讀cache時,dma已經開始(拿到最新的) 或 dma關閉(不會被改)
- 會導致cache不一致!! (因為直接改mem,但cache不知道)
- 不然cpu就要中斷自己去做
- 由cpu啟動dma,剩下的事情就是dma controller的工作
- 讓device可以直接操作記憶體,像是device把資料copy到mem去
mutex & semaphore
- mutex
- 受益人數: 一人
- 誰能改變狀態(上鎖): a. 還沒上鎖: 所有人 b. 上鎖了: 上鎖的人
- 使用場域: 保護critical zone
- semaphore
- 受益人數: 看設定多少
- 誰能改變狀態(記數): 誰都ok
- 使用場域: 同步時的signal
Big Endian & Little Endian
資料放進記憶體中的時
- Big-Endian(Network Order): 最高位的位元組會放在最低的記憶體位址上
- Little-Endian: 最高位的位元組放在最高的記憶體位址上
記憶體的最高在右邊,但資料的最高在左手邊
High -> 12345678 <- Low
Low -> a[0], a[1], a[2], a[4] <- High
#include <stdio.h>
#include <stdint.h>
#include <arpa/inet.h>
typedef union {
uint32_t l;
unsigned char c[4];
} EndianTest;
// 輸出位元組順序
void printBytes(uint32_t x) {
EndianTest et;
et.l = x;
for (int i = 0; i < 4; i++) {
printf("0x%02X ", et.c[i]);
}
printf("n");
}
int main() {
uint32_t x = 0x12345678;
// big: 12, 34, 56, 78
// little: 78, 56, 34, 12
printf("0x%X 在記憶體中的儲存順序:", x);
printBytes(x);
uint32_t n = htonl(x);
printf("0x%X 在網路中的傳輸順序:", x);
printBytes(n);
}
how to IPC
- pipe(named pipe)
- popen
- message queue
- semophore
- signal
- shared memory
- socket
- file
deadlock condition
race condition: 輸出依據不受控制的事件出現順序或者出現時機 Critical section: access共享資源的code synchronization: 協調讓thread使用Critical section在時間上一致與統一 Cache coherence: 快取一致性 (為什麼放這邊?,可以看volatile)
- 資源唯一(互斥) => a node in a graph
- 不會被搶(不可搶占) => node cant be removed
- 拿著並等別的資源(占有且等待) => an edge to other node
- 別人也在等我(循環等待) => cycle
thread & process
- process: OS的資源分配單位,彼此不影響
- thread: 程式的執行單位,互相影響 a. a kernel thread to a user thread: linux’s clone(child process, LWP) b. a kernel thread to many user threads c. many kernel threads to many user threads
how to talk to kernel
- virtual filesystems(e.g. /proc, /sys, configfs, relayfs): kernel <-> user, exchange data
- netlink/UDP socket: kernel <-> user, exchange data
- mmap: kernel <-> user, exchange data
- syscall: user -> kernel, invoke function
- ioctl: kernel <-> user, exchange data
- signal from kernel: kernel -> user, invoke handler
- upcall: kernel -> user, invoke a function
volatile
就是叫compiler每次都乖乖重新拿值,不然可能有神秘的優化把變數消滅掉
用處
- register
- 在multi-thread共用的變數
- 一個中斷服務子程序中會訪問到的非自動變量(Non-automatic variables)
Can we use “const” and “volatile” in the same variable?
extern const volatile unsigned int rt_clock;
用在監看reg的值的時候
malloc、calloc、realloc
- malloc: 就是allocate
- calloc: allocate + 初始值
- realloc: resize但是不一定從原本的位置開始,所以會copy舊資料到新位置
pointer size
看記憶體是幾位元的
- 64: 8 bytes
- 32: 4 bytes
OO in C
struct與透過method控制struct就是封裝 繼承要利用c的struct特性
因為struct只是去算field的offset,所以只要
- 同一個位置
- 同樣長度 就可以當成child class (484與golang很像)
struct A {
int a;
};
struct B {
int a;
// extend
int b;
};
void getA(A* this) {
printf("%d\n", this->a);
}
void getB(B* this) {
printf("%d %d\n", this->a, this->b);
}
int main() {
B y{30,20};
getA((A*)y);
}
那多形? struct自己存實作的fucntion ptr
macro
一些工具
- concat
#define cons(a,b) a##b
// cons(1,2) => 12
- 可變參數(預設參數),如果有尾巴的逗號會被吃掉 (C99)
#define foo(must, ...) _foo(must, (foo_args){.a = 8, .b = 3.14, __VA_ARGS__});
// foo('D', .b = 9, .a = 2);
- to_string
#define STR(s) #s
// STR(123) => "123"
- _Generic (C11, 就是泛型)
#define foo(a, b) \
_Generic((a), \
int: func1, \
double: _Generic((b), \
int : func2, \
double: func3 \
) \
)(a, b)
使用時要注意
- 用括號
#define pow(a) (a) * (a)
// pow(1+2) => (1+2) * (1+2)
- 如果有temp變數要加大括號,不然會汙染到原本的scope
#define swap(a, b) { \
int temp = a; \
a = b; \
b = temp; \
}
- 幫數字標上type
#define NUM (100*123*234UL)
// 注意: 型別要放在數字後面!!
// U for unsigned
// L for long int or long float
// F for float
// default: double or int
- 如果要用macro在另一個macro要多一層讓macro先展開
#define A 2
#define CONS(a,b) a##b
// CONS(A,A) => AA
#define _CONS(a,b) a##b
#define CONS_GOOD _CONS(a,b)
// CONS_GOOD(A,A) => _CONS(2,2) => 22
useful macro & case study
#include <stdio.h>
#define debug(fmt, ...) { \
fprintf(stderr, "(%s:%d) "fmt"\n", __FILE__, __LINE__, ##__VA_ARGS__); \
}
int main(void) {
debug("%s %d", "Shit happen!", 1);
return 0;
}
#define hash_hash # ## #
#define mkstr(a) # a
#define in_between(a) mkstr(a)
#define join(c, d) in_between(c hash_hash d)
//join(x, y)
//in_between(x hash_hash y)
//in_between(x ## y)
//mkstr(x ## y)
default args: 在函數做設定
#include <stdio.h>
#define f(...) def_f((f_args) {__VA_ARGS__})
typedef struct {
int i;
double j;
} f_args;
void real_f(int i, double j) {
printf("%i %f\n",i,j);
}
void def_f(f_args args) {
int i = args.i ? args.i : 10;
double j = args.j ? args.j : 10.5;
real_f(i,j);
}
int main() {
f(3,8);
f(.j=100.2, .i=4);
f(2);
f(.j=45.3);
f();
f(12,);
return 0;
}
default args: 在struct(macro展開時)做設定
#include <stdio.h>
#define f(...) def_f((f_args){.i=10, .j=10.5, __VA_ARGS__})
typedef struct {
int i;
double j;
} f_args;
void real_f(int i, double j) {
printf("%i %f\n",i,j);
}
void def_f(f_args x) {
real_f(x.i,x.j);
}
int main() {
//f(3,8);
f(.j=100.2, .i=4);
//f(2);
f(.j=45.3);
f();
//f(12,);
return 0;
}
floating point
- 不要直接比較,要變成看相對誤差
abs((0.1+0.2)-0.3) < epsilon
少用float做直接運算,轉成整數
使用另外設計的Lib
調整算式
ISR
__interrupt double compute_area(double radius) // 1. no args (這應該是上半部)
{
double area = PI * radius * radius; // 2. kernel space 用float?!
printf("\nArea = %f", area); // 3. 在中斷跑IO!?
return area; // 4. irq沒有return,syscall有
}
pitfall
auto cast
有unsigned就會自動變成unsigned
void foo(void)
{
unsigned int a = 6;
int b = -20;
(a+b > 6) ? puts("> 6") : puts("<= 6"); // > 6
}
type size
只有char確定是1byte!!
所以下面的code可能會出事,因為unsigned int
不一定是4bytes
unsigned int zero = 0;
unsigned int compzero = 0xFFFF; /*1's complement of zero */
type | size |
---|---|
char | 1 byte |
unsigned char | 1 byte |
signed char | 1 byte |
int | 2 or 4 bytes |
unsigned int | 2 or 4 bytes |
short | 2 bytes |
unsigned short | 2 bytes |
long | 8 bytes or (4bytes for 32 bit OS) |
unsigned long | 8 bytes |
p++ & ++p
就算有括號,p++
的inc還是後做,++p
的inc還是先做
int a[5]={1,2,3,4,5};
int *p=a;
*(p++)+=123;
*(++p)+=123;
//124 2 126 4 5
typedef
#define dPS struct s *
typedef struct s * tPS;
dPS p1,p2; // => struct s * p1,p2;
tPS p3,p4;// => struct s *p1,*p2;
pointer & array type
先看array,再看pointer,有括號就先看括號
int a[10]; // An array of 10 integers
int *a[10]; // An array of 10 pointers to integers
// int ((*a)[10])
int (*a)[10]; // A pointer to an array of 10 integers
int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer
int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer
const & pointer
int const *a; // ptr的內容固定
int * const a; // 被指到的int固定
const int const * a; // 兩個都固定
計組
Pipeline
- instruction fetch
- instruction decode/fetch register
- instruction execute/ branch or jump
- memory access
- to mem
- write back
- to reg
因為現在是所有指令共用datapath,所以要pipeline reg讓stage去load需要的資料去完成每個階段的任務
harzards
- Data Hazard
- LoadStore (RAW): 還沒寫完就被讀(拿到舊的值)
- StoreStore (WAW): 前面還沒寫完,後面已經寫進去了
- StoreLoad (WAR): 來沒讀完就被寫(拿到未來的值)
- sol
- compiler 或是 cpu的 stall
- forwarding: 把需要的資訊丟到後面的pipeline reg,讓後面的stage先做事
- Control Hazard
- if 的 bool還沒被算出來!!
- sol
- stall
- 分支預測(猜)
- Structural hazard
- 在pipeline上指令的需要同一個資源
- sol
- stall
- 亂序執行
Network Programming
server
- socket
new socket()
- bind
- 填ip, port
- listen
socket.start()
- accept (可以去看block-nonblock-sync-async補一下block與non-block的知識)
- loop -> get socket
- read / write
- close
client
- socket
- connect
- write / read
- close
傳struct
不能直接傳,大小頭、padding、type的size不一定會一樣,會可能會轉不回來
- 轉成string
- 用htonl與ntohl,自己把int轉一轉
- host order to network order long (integer, s是short integer)
- network order to host order long (integer, s是short integer)
float就要自己設計格式,沒有htonl與ntohl可以用,不然就是去找序列化的lib,像protobuf
pipe
- pipefs
- create a file in pipefs, which is in mem
- return 2 fd, one for writing, another for reading
how tcpdump works
- 透過libpcap的api生bpf