顯示具有 c 標籤的文章。 顯示所有文章
顯示具有 c 標籤的文章。 顯示所有文章

2022年10月21日 星期五

c 語言數值運算的 type

惟以一人治天下. 豈為天下奉一人
c 語言 3 + 5 時, 3, 5 是什麼 type, 3+5 計算之後又是什麼 type? 規格書有定義, 不過有個簡單的方法可以查看, 來看看 gcc 編譯時, printf 秀出的資訊。

測試環境 linux 64 bit:

list 1. a1.c
1 #include <stdio.h>
2 
3 int main(int argc, char *argv[])
4 {
5   printf("%llu\n", 3);  
6   return 0;
7 }
m
1 descent@u64:~$ gcc -Wall a1.c -o a1
2 a1.c: In function ‘main’:
3 a1.c:5:14: warning: format ‘%llu’ expects argument of type ‘long long unsigned int’, but argument 2 has type ‘int’ [-Wformat=]
4    printf("%llu\n", 3);
5            ~~~^
6            %u

我用 %llu 去印, gcc 發出警告, 說第二個參數是 int 不是 long long unsigned int。所以 3 是 int。再來看一個有趣的例子。

a1.c
1 #include <stdio.h>
2 
3 int main(int argc, char *argv[])
4 {
5   printf("%lld\n", 0x80000000);
6   return 0;
7 }
m1
1 descent@u64:~$ gcc -Wall a1.c -o a1
2 a1.c: In function ‘main’:
3 a1.c:5:14: warning: format ‘%lld’ expects argument of type ‘long long int’, but argument 2 has type ‘unsigned int’ [-Wformat=]
4    printf("%lld\n", 0x80000000);
5            ~~~^
6            %d

0x80000000 是 unsigned int

a1.c
1 #include <stdio.h>
2 
3 int main(int argc, char *argv[])
4 {
5   printf("%lld\n", 1 << 31);
6   return 0;
7 }
m2
1 a1.c: In function ‘main’:
2 a1.c:5:14: warning: format ‘%lld’ expects argument of type ‘long long int’, but argument 2 has type ‘int’ [-Wformat=]
3    printf("%lld\n", 1 << 31);
4            ~~~^
5            %d

1 << 31 的 type 是 int, 1 << 31 「看起來」很像是 0x80000000, 但是 type 是 int, list 1 看到 3 是 int, 所以可以推測 1 是 int, 31 是 int, 1 << 31 之後的結果也是 int, 「但是」 1 << 31 是一個 undefined behavior,

ref N1570 p95 第4點

The result of E1 << E2 is E1 left-shifted E2 bit positions; vacated bits are filled with
zeros. If E1 has an unsigned type, the value of the result is E1 × 2E2, reduced modulo
one more than the maximum value representable in the result type. If E1 has a signed
type and nonnegative value, and E1 × 2E2 is representable in the result type, then that is
the resulting value; otherwise, the behavior is undefined.

英文不太好看, 大概是這樣的意思, 1 << 31 是 0x80000000 int 無法存入這個數值, 所以是 undefined behavior, unsigned int 才能存 0x80000000。

a1.c
1 #include <stdio.h>
2 
3 int main(int argc, char *argv[])
4 {
5   printf("%lld\n", 0xffffffff);
6   return 0;
7 }
m3
1 descent@u64:~$ gcc -m32 -Wall a1.c -o a1
2 a1.c: In function ‘main’:
3 a1.c:5:14: warning: format ‘%lld’ expects argument of type ‘long long int’, but argument 2 has type ‘unsigned int’ [-Wformat=]
4    printf("%lld\n", 0xffffffff);
5            ~~~^
6            %d

使用 -m32 是因為 long 在 linux 64/32 會有不同的解釋, 先用 linux 32 環境為主來觀察。0xffffffff 是 unsigned int, 如果 0xffffffff + 1 之後, type 會是 long long unsigned int 或是 long long int 嗎?

a1.c
1 #include <stdio.h>
2 
3 int main(int argc, char *argv[])
4 {
5   printf("%lld\n", 0xffffffff + 1);
6   return 0;
7 }
m5
1 descent@u64:~$ gcc -m32 -Wall a1.c -o a1
2 a1.c: In function ‘main’:
3 a1.c:5:14: warning: format ‘%lld’ expects argument of type ‘long long int’, but argument 2 has type ‘unsigned int’ [-Wformat=]
4    printf("%lld\n", 0xffffffff + 1);
5            ~~~^
6            %d

0xffffffff + 1 還是 unsigned int, 所以會發生什麼事情? unsigned int 裝不下 0xffffffff + 1, 所以計算的數值有會問題,

a1.c
1 #include <stdio.h>
2 
3 int main(int argc, char *argv[])
4 {
5   printf("%d\n", 0xffffffff + 1llu);
6   return 0;
7 }
m6
1 descent@u64:~$ gcc -m32 -Wall a1.c -o a1
2 a1.c: In function ‘main’:
3 a1.c:5:12: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘long long unsigned int’ [-Wformat=]
4    printf("%d\n", 0xffffffff + 1llu);
5            ~^
6            %lld


a1.c
1 #include <stdio.h>
2 
3 int main(int argc, char *argv[])
4 {
5   printf("%d\n", 4294967296);
6 
7   return 0;
8 }

而用了 0xffffffff + 1llu, 整個計算結果就變成 long long unsigned int, 就可以完整存下計算結果了, 詳細過程是怎麼樣呢? 可能是 Usual Arithmetic Conversion 描述的那樣, 有個很複雜的規則, 我不確定是怎麼套用。

m7
1 descent@u64:~$ gcc -m32 -Wall a1.c -o a1
2 a1.c: In function ‘main’:
3 a1.c:5:12: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘long long int’ [-Wformat=]
4    printf("%d\n", 4294967296);
5            ~^
6            %lld

4294967296 是 long long int

a1.c
1 #include <stdio.h>
2 
3 int main(int argc, char *argv[])
4 {
5   printf("%d\n", 4294967296 + 0xfffffffflu);
6 
7   return 0;
8 }
m8
1 a1.c: In function ‘main’:
2 a1.c:5:12: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘long long int’ [-Wformat=]
3    printf("%d\n", 4294967296 + 0xfffffffflu);
4            ~^
5            %lld

4294967296 + 0xfffffffflu 用 long long int 就可以存, 所以相加後的 type 是 long long int。

想查閱 C 規格書的話在 N1570 6.4.4.1 integer constants。

數值計算規則非常複雜, 並不是只有這樣, C 規格書定義了複雜的規則。

ref:
  1. C 語言的 usual arithmetic conversion
  2. c 語言中的 0x7fffffff 和 0xffff0000 是什麼 type?

2022年3月25日 星期五

怪異的 c 語法

在 telegram 上看到有關 c 語言的一些怪異語法。
覺得有趣, 來分析看看

twitter 上的程式碼是圖片, 不太習慣, 自己打成文字版本。

a1.c
1 #include <stdio.h>
2
3 int main(int argc, char *argv[])
4 {
5   puts("-0.5" + 1);
6   return 0;
7 }


把 "-0.5" 想成 const char *str="-0.5", str + 1 是什麼呢? 當然就是 0.5 了。

a2.c
1 #include <stdio.h>
2
3 int main(int argc, char *argv[])
4 {
5   printf("%d\n", 50 ** "2");
6   return 0;
7 }


一樣的道理, 把 "2" 想成 const char *str2 = "2", *str2 就是 '2', 等於是作 50 * '2'。

a3.c
 1 #include <stdio.h>
 2
 3 int main(int argc, char *argv[])
 4 {
 5   int x = 5;
 6
 7   // while x goes to 0
 8   while(x --> 0)
 9   {
10     printf("%d ", x);
11   }
12
13   return 0;
14 }
15 result: 4 3 2 1 0 


這個只是
 8   while(x --         >           0)

盡量不要寫 a3.c L8 的語法, 你搞不清楚 5 和 0 比, 還是 4 和 0 比, 這寫法是 5 和 0 比, L10 印出 4, 依序下去。

2022年3月18日 星期五

要用幾個變數來接受 c scanf 的輸入

初學者在學 c 的鍵盤輸入時, 最困擾的通常是要宣告幾個變數來接受鍵盤的資料, 以 scanf 為例, 如果宣告一個 int input, 不就只能接受一個輸入嗎? 如果輸入兩筆資料時要怎麼辦? 30 筆的時候該怎麼辦? 好像沒辦法事先知道要宣告幾個變數來接收鍵盤輸入。

這時候先學習 c++ 的好處就來了, 不需要先寫可怕的 malloc 來處理這件事情, 用 std::vector 就可以搞定, 如果你不是用 c++ 而是學 c, 那就得先刻一個類似 vector 的資料結構, 通常這時候也應該會寫 list, 剛好派上用場。

初學的時候一般會用一個折衷的辦法, 先輸入一個數字, 例如 5, 代表之後要輸入 5 筆資料, 再用 malloc 5 個 int 大小的記憶體區間, 厄, 不太好是吧, 還不如一次就把這些觀念帶上, 先用 c++ vector, 再用 c 自己實做一個 vector/list, 這本來就是本科系無法迴避的課題。scanf 可沒想像的簡單。如果是我來教學, 我會這麼做, 讓初學者知道有這樣的資料結構, 等學習一段時間之後, 再去實做那種資料結構。

list 1 s.cpp
 1 #include <cstdio>
 2 #include <vector>
 3 
 4 using namespace std;
 5 
 6 int main(int argc, char *argv[])
 7 {
 8   vector<int> vals;
 9   int input=0;
10   int ret;
11 
12   while(1)
13   {
14     ret = scanf("%d", &input);
15     printf("ret: %d\n", ret);
16     printf("input: %d\n", input);
17     if (ret)
18     {
19       vals.push_back(input);
20     }
21     if (ret == EOF || ret == 0)
22     {
23       break;
24     }
25   }
26 
27   for (auto &i : vals)
28   {
29     printf("i: %d\n", i);
30   }
31   return 0;
32 }

我用 c++ 就偷懶一下了。scanf 對 c 初學者來說, 門檻真的太高了, 它比想像中的還要難。

另外一個類似的問題, 如果你要輸入一個字串, 要宣告 char str[10]; 確定這個夠嗎? 如果輸入的字串有 20 char 怎麼辦? POSIX.1-2008 親切的提供了 m 來處理這個問題, 參考 list 2 的用法, 然而在更早之前 GNU 提供了 a, 所以有點混亂, 更麻煩的是 a 現在提供給浮點數用, 而且成為標準, 但如果你用的是夠新的 c 編譯器, 應該有 m 可以用。windows/vs, mac/clang 請自行確認有沒有 m 可以用。請記得不用時要 free 這個 str, 另外傳給 scanf 時, 是傳指標的指標 &str(char **), 相信整死 c 初學者了。

list 2 s2.cpp
25   char *str;
26
27   ret = scanf("%ms", &str);
28
29   printf("str: %s\n", str);
30
31   free(str);

還沒完, 如果格式轉換出錯時怎麼辦, %d 結果輸入了 abc 呢? 這個真的整死我, 花了不少時間才搞定, 因為轉換失敗, 這時候 abc 還存在 stdin buffer 裡頭, 如果使用 list 1. L12 用 while 去抓, 在我的平台會一直無窮迴圈, 因為 abc 一直存在, scanf 會一直格式錯誤, return 0, errno 會得到 EILSEQ。

解法很直覺, 清掉 stdin buffer 就好了吧, 出動 fflush(stdin), 結果沒用, 再補上 clearerr(stdin), 也沒用, 靠, stdin buffer 清不掉阿, fflush 名過其實。後來找到 __fpurge(stdin) 終於清掉 stdin buffer。這時候 scanf 就又會等在那邊等著使用者按下鍵盤輸入了。

linux man page 提到: For input streams associated with seekable files (e.g., disk files, but not pipes or terminals), 也許 stdin 不算是 seekable files 吧! 所以 fflush(stdin) 無效。另外「C程式語言」B-4 提到 fflush 對於 input streams, 結果未定義 (undefined behavior)。

__fpurge(stdin) 不確定其他平台有沒有, 請自行查詢。另外一個有可攜性的作法就是把 stdin buffer 讀出來丟掉。

int c;
while ((c = getchar()) != '\n' && c != EOF);

我本來只想寫第一段那個而已, 沒想到後來補了這麼多, scanf 難阿!

ref:
  1. C語言——使用scanf函式時需要注意的問題
  2. I am not able to flush stdin

2022年3月13日 星期日

printf 的 integer promotion

巧者勞力, 智者勞心
p.c
 1 #include <stdio.h>
 2
 3 int main(int argc, char *argv[])
 4 {
 5   unsigned char a=0x80;
 6   signed char b=0x80;
 7
 8   printf("unsigned char a: %#x, signed char b: %#x\n", a, b);
 9   printf("unsigned char a: %#x (%d), signed char b: %hhx (%hhd)\n", a, a, b, b);
10   return 0;
11 }

list 1 執行結果
 1 unsigned char a: 0x80, signed char b: 0xffffff80
 2 unsigned char a: 0x80 (128), signed char b: 80 (-128)


list 1 是執行結果, L1 顯示了 signed char b: 0xffffff80, 不是預期的 0x80, 有點奇怪是吧? 引用之前的文章 - compiler [7] - code generator - funcall call, pass argument
function 參數的傳遞比想像中複雜, 當 function 沒有 prototype 時或是使用 K&R style 的宣告或是 ... 這種參數 - ex: printf(const char *format, ...), 會發動 integer promtion, 這很好理解, 可以參考《“对于那些没有原型的函数,传递给函数的实参将进行缺省参数提升”是什么意思?
printf 的參數是 ... 所以會發動 integer promtion, integer promtion 之後, signed char b 0x80 的 -128 變成了 4bytes 的 -128, 也就是 0xffffff80, 這便是 %#x 印出 0xffffff80 的原因。

cpu 用什麼指令做這件事情, 反組譯一下觀察, list 2 L716, 717 的指令 movsbl, movzbl 就是在做這件事情。由於比較熟悉 x86-32, 所以編譯成 x86-32 來觀察, 其他平台應該也有類似的指令, 就不一一觀察了。

list 2. p.c.dis 編譯指令 gcc -static -no-pie -fno-pic -m32 -g p.c -o p
     1 
     2 p:     file format elf32-i386
   705 08049815 <main>:
   706  8049815:	8d 4c 24 04          	lea    0x4(%esp),%ecx
   707  8049819:	83 e4 f0             	and    $0xfffffff0,%esp
   708  804981c:	ff 71 fc             	push   -0x4(%ecx)
   709  804981f:	55                   	push   %ebp
   710  8049820:	89 e5                	mov    %esp,%ebp
   711  8049822:	53                   	push   %ebx
   712  8049823:	51                   	push   %ecx
   713  8049824:	83 ec 10             	sub    $0x10,%esp
   714  8049827:	c6 45 f7 80          	movb   $0x80,-0x9(%ebp) # unsigned char a=0x80
715 804982b: c6 45 f6 80 movb $0x80,-0xa(%ebp) # signed char b=0x80
716 804982f: 0f be 55 f6 movsbl -0xa(%ebp),%edx # signed char b=0x80 717 8049833: 0f b6 45 f7 movzbl -0x9(%ebp),%eax # unsigned char a=0x80 718 8049837: 83 ec 04 sub $0x4,%esp 719 804983a: 52 push %edx 720 804983b: 50 push %eax 721 804983c: 68 08 30 0b 08 push $0x80b3008 722 8049841: e8 da 85 00 00 call 8051e20 <_IO_printf> 723 8049846: 83 c4 10 add $0x10,%esp 724 8049849: 0f be 5d f6 movsbl -0xa(%ebp),%ebx 725 804984d: 0f be 4d f6 movsbl -0xa(%ebp),%ecx 726 8049851: 0f b6 55 f7 movzbl -0x9(%ebp),%edx 727 8049855: 0f b6 45 f7 movzbl -0x9(%ebp),%eax 728 8049859: 83 ec 0c sub $0xc,%esp 729 804985c: 53 push %ebx 730 804985d: 51 push %ecx 731 804985e: 52 push %edx 732 804985f: 50 push %eax 733 8049860: 68 34 30 0b 08 push $0x80b3034 734 8049865: e8 b6 85 00 00 call 8051e20 <_IO_printf> 735 804986a: 83 c4 20 add $0x20,%esp 736 804986d: b8 00 00 00 00 mov $0x0,%eax 737 8049872: 8d 65 f8 lea -0x8(%ebp),%esp 738 8049875: 59 pop %ecx 739 8049876: 5b pop %ebx 740 8049877: 5d pop %ebp 741 8049878: 8d 61 fc lea -0x4(%ecx),%esp 742 804987b: c3 ret

另外 printf 提供了 %hh 來印出 signed char 或是 unsigned char 變數。

ref:

2019年5月24日 星期五

implement itoa()

itoa() 並不是 c 標準, 有些平台有支援, 但 gnu c 並沒有支援這個。

而在 bare-metal 環境會很需要把整數轉成字串, 搭配 print 之類的函式來除錯; itoa 實作並不難, 這裡有個範例可以抄。

http://www.retro11.de/ouxr/43bsd/usr/ingres/source/gutil/itoa.c.html

不過有個討厭的數學問題:
1 *j-- = i % 10 + '0';
2 i /= 10;

i%10 如果 i 是負數會怎麼樣, 以下的 c++ 程式碼會印出什麼?

cout << (-8) % 16 << endl;

在我的平台是 -8, 但他不是一個固定答案, 根據不同語言的實作有不同的結果。而如果是 -8 的話, 會造成取到錯誤的 array index, 程式不至於會當掉, 但會得到錯誤結果。

ref:
负数究竟是如何取模的?

很久之前就知道這問題, 不過當時這並不是太重要的問題, 我一直沒理他, 現在學習告個段落, 該是好好正視這問題。

所以改善的作法是不要用負數取餘數, 改用正整數來取餘數。

目標是這樣:
我希望傳入 -1 時, 可以得到 -1 的字串; 傳入 0x80000000 可以得到 80000000 的字串。

itoa test
183   //int v = 0x80000000;
184   int v = -1;
185   char str[32];
186   itoa_32(v, str, 16);
187   printf("str: %s\n", str);
188
189   itoa_32(v, str, 10);
190   printf("str: %s\n", str);
191
192   return 0;

數字: -1
str: FFFFFFFF (16 進位)
str: -1       (10 進位)

數字: 0x80000000
str: 80000000    (16 進位)
str: -2147483648 (10 進位)

以下是最後修改的版本:
itoa.c
 2 char* itoa_32(int n, char* str, int radix)
 3 {
 4   char digit[]="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
 5   char* p=str;
 6   char* head=str;
 7   unsigned int val;

12   if (n==0)
13   {
14     *p++='0';
15     *p=0;
16     return str;
17   }
18   if (radix == 10 && n < 0)
19   {
20     val = (-n);
21   }
22   else
23     val = n;
24 
25   while(val)
26   {
27     *p++=digit[val%radix];
28     val/=radix;
29   }
30 
31   if (radix == 10 && n < 0)
32     *p++='-';
33 
34   *p=0;
36   for (--p; head < p ; ++head, --p)
37   {
38     char temp=*head;
39     *head=*p;
40     *p=temp;
41   }
43   return str;
44 }
45 

itoa.c L27 一律用 val 這個 unsigned int 來取餘數。

這個 itoa 還有其他問題, 例如沒有檢查 str 的大小, 是有可能超過邊界的, 不過由於知道 int 最大是 11 個位數 (包涵負號), 把 str array 設定大一點, 勉強可以避開這問題。

2018年4月27日 星期五

Multi-dimensional Arrays in C - c 的多維陣列

寫俄羅斯方塊時, 搞反了 2 維陣列的維度, 逼自己好好反省一下。

m_array.c
 1 #include <stdio.h>
 2 
 3 int main(int argc, char *argv[])
 4 {
 4.5   // int daytab[3][13] 每 row 有 13 個元素
 5   unsigned char a[5][4][6][8];
 6 
 7   printf("a: %p\n", a);
 8   printf("&a[3][3][2][7]: %p\n", &a[3][3][2][7]);
 9   printf("offset: %d\n", (&a[3][3][2][7]) - (&a[0][0][0][0]));
10   return 0;
11 }

a: 0x7fff98a74220
&a[3][3][2][7]: 0x7fff98a74507
offset: 743


m_array.c L4.5 宣告了 2 維陣列, 誰是 3, 誰是 13 呢? 老是搞不清楚, 以 row, column 來說, row 是 13, column 是 3。

4 維空間, 厄 ... 是 4 維陣列無法用 row, column 來說明, 所以遇到 unsigned char a[5][4][6][8] 該怎麼想呢? 長的像以下的圖, 下圖只到 [4][6][8], 有 5 個以下的圖, 就是 [5][4][6][8] 了。

a[3][2][5][7] 的位址該怎麼計算呢?

a
[3] - a3
[2] - b2
[5] - c5
[7] - d7

在 d7 的那個紅色位置。

1


1 2 3 4 5 6 7 8

2







3







4







5







6







2

1







2







3







4







5 , 1 2 3 4 5 6 7 8

6







3























































4
























































2 [4][6][8]

a3
1


1 2 3 4 5 6 7 8

2







3







4







5







6







b2

1







2







3







4







c5 , 1 2 3 4 5 6 d7 8

6







3























































4
























































4 [4][6][8]

5 [4][6][8]

2017年5月19日 星期五

程式真的要寫成這麼聰明嗎?

Alan Perlis, Alan Perlis QuotesA programming language is low level when its programs require attention to the irrelevant.

代码执行的效率》裡頭提到一個效率問題,

代码执行的效率》有效率的改法
if (data[j] >= 128)
sum += data[j];
变成:
int t = (data[j] - 128) >> 31;
sum += ~t & data[j];

這個看似聰明的改法帶來了什麼樣難以察覺的陷阱呢?

不想看答案的可以先想想看。

b.cpp
 1 #include <stdio.h>
 2 #include <stdint.h>
 3 #include <iostream>
 4 
 5 using namespace std;
 6 
 7 int main(int argc, char *argv[])
 8 {
 9   int data[10] = {1,2,3,4,200,6,7,8,300,10};
10   int sum=0;
11 
12   for (int j=0 ; j < 10 ; ++j)
13   {
14     int t = (data[j] - 128) >> 31;
15     //printf("     %u ## t: %u, ~t: %u, ~t & data[j]: %u\n", j, t, ~t, ~t & data[j]);
16     cout << "cout " << j << " ## t: " << t << ", ~t: " << ~t << ", ~t & data[j]: " << (~t & data[j]) << endl;
17     sum += ~t & data[j];
18     #if 0
19     if (data[j] >= 128)
20       sum += data[j];
21     #endif
22   }
23   //printf("sum: %u\n", sum); 
24   cout << "sum: " << sum << endl;
25   return 0;
26 }

b.cpp 執行結果
cout 0 ## t: -1, ~t: 0, ~t & data[j]: 0
cout 1 ## t: -1, ~t: 0, ~t & data[j]: 0
cout 2 ## t: -1, ~t: 0, ~t & data[j]: 0
cout 3 ## t: -1, ~t: 0, ~t & data[j]: 0
cout 4 ## t: 0, ~t: -1, ~t & data[j]: 200
cout 5 ## t: -1, ~t: 0, ~t & data[j]: 0
cout 6 ## t: -1, ~t: 0, ~t & data[j]: 0
cout 7 ## t: -1, ~t: 0, ~t & data[j]: 0
cout 8 ## t: 0, ~t: -1, ~t & data[j]: 300
cout 9 ## t: -1, ~t: 0, ~t & data[j]: 0
sum: 500

哇! 真的可以計算正確結果耶! 估計你開始佩服想出這個寫法的人了。再來看看另外的例子 a.cpp。

a.cpp
 1 #include <stdio.h>
 2 #include <stdint.h>
 3 #include <iostream>
 4 
 5 using namespace std;
 6 
 7 int main(int argc, char *argv[])
 8 {
 9   uint32_t data[10] = {1,2,3,4,200,6,7,8,300,10};
10   uint32_t sum=0;
11 
12   for (int j=0 ; j < 10 ; ++j)
13   {
14     uint32_t t = (data[j] - 128) >> 31;
15     //printf("     %u ## t: %u, ~t: %u, ~t & data[j]: %u\n", j, t, ~t, ~t & data[j]);
16     cout << "cout " << j << " ## t: " << t << ", ~t: " << ~t << ", ~t & data[j]: " << (~t & data[j]) << endl;
17     sum += ~t & data[j];
18     #if 0
19     if (data[j] >= 128)
20       sum += data[j];
21     #endif
22   }
23   //printf("sum: %u\n", sum); 
24   cout << "sum: " << sum << endl;
25   return 0;
26 }

a.cpp 執行結果<
cout 0 ## t: 1, ~t: 4294967294, ~t & data[j]: 0
cout 1 ## t: 1, ~t: 4294967294, ~t & data[j]: 2
cout 2 ## t: 1, ~t: 4294967294, ~t & data[j]: 2
cout 3 ## t: 1, ~t: 4294967294, ~t & data[j]: 4
cout 4 ## t: 0, ~t: 4294967295, ~t & data[j]: 200
cout 5 ## t: 1, ~t: 4294967294, ~t & data[j]: 6
cout 6 ## t: 1, ~t: 4294967294, ~t & data[j]: 6
cout 7 ## t: 1, ~t: 4294967294, ~t & data[j]: 8
cout 8 ## t: 0, ~t: 4294967295, ~t & data[j]: 300
cout 9 ## t: 1, ~t: 4294967294, ~t & data[j]: 10
sum: 538

發現結果不正確了, 僅僅是 type 的不同, 程式就不正確了。你願意用這個演算法嗎? 這個演算法在某些時候是對的, 某些時候是錯的, 你能掌握在什麼時機才是正確的嗎?

第一點: 這寫法很難懂
第二點: 不一定總是正確

如果你能掌握型別的話, 那還有一個規則是你不能掌握的。最後在提醒一個 c 語言規則, 有號數的右移運算, 是 implementation-defined (ref: 1.2. 移位运算)。

當然查閱 c 規格書最具有權威性, 但那個不好看, 如果要在精確一點的中文資料, 可以參考《标准C语言指南》6.18 (p341), 將移位運算式做了詳細的說明, 看完之後應該會覺得, 嗯 ... c 真難。

相信這麼一來, 之前的佩服感應該是完全消失了, c 沒有這麼簡單, 她是程式語言, 不是數學。

我不熟習 java, 有人可以說說 java 的行為嗎?

既然談到 Bitshift Operators 順便說說之前遇到的一個問題。

t.c
 1 #include <stdio.h>
 2 #include <stdint.h>
 3 int main(int argc, char *argv[])
 4 {
 5   uint64_t temp;
 6
 7   temp = (1 << 32);
 8   printf("temp: %lu\n", temp);
 9   return 0;
10 }

t.c 看似理所應當的程式碼, c 編譯器發出了 warning:

warning
1 t.c: In function 'main':
2 t.c:7:13: warning: left shift count >= width of type [-Wshift-count-overflow]
3    temp = (1 << 32);

怎麼回事? 1 的 type 是 int, 不是 uint_64_t, 也不是其他 type, 就是 int, 在我的平台, 是 32bit, 而 shift 32 bit 超出了 32bit 的範圍, 這是 Undefined (ref: 1.2. 移位运算), 這回我參考了 c 標準規格書 (ref: figure 1, 第 3 點), 而不是如人所想因為等號左邊有個 uint64_t temp 就會將結果轉型成 uint64_t, 這是 c 語言很難相處之道。

figure 1. N1570 6.5.7

解法: 1ul << 32, 明白告訴 c 編譯器, 我的 1 是 unsigned long (在我的環境是 64bit)

2017年4月7日 星期五

c 語言中的 0x7fffffff 和 0xffff0000 是什麼 type?

在 c 程式碼上的一個數字, 他們是什麼型別呢? int, unsigned int, long int ... 你會不會有類似的疑惑呢? 而不知道他們是什麼型別有什麼要緊的呢? 有經驗的程式員馬上就聯想到會不會有 overflow 的問題。

5+253 如果 5 是 unsigned char, 253 是 unsigned char, 那加起來不就超過 char 的大小了嗎? 但是
cout << 5+300 << endl;
可以正常印出 258, 而不是被截掉的數值。

那這兩個數字 0x7fffffff 和 0xffff0000 是什麼 type 呢?

為什麼 0x7fffffff 不是 unsigned int?《linux c 編程 一站式學習》有答案, 我想驗證看看是不是真的, 其實應該讀 c spec, 但你知道的 ...

該怎麼驗證呢? 使用 c++ rtti, 以下程式碼使用 c++ rtti 來得到 type。

c++filt -t 可以把神秘的字母轉成看得懂的 type。

a.cpp
 1 #include <typeinfo>
 1 #include <typeinfo>
 2 #include <iostream>
 3 using namespace std;
 4 
 5 #include <sys/types.h>
 6 #include <sys/stat.h>
 7 #include <fcntl.h>
 8 #include <unistd.h>
 9 #include <sys/mman.h>
10 
11 
12 int main(int argc, char *argv[])
13 {
14   cout << "0x7fffffff type:" << typeid(0x7fffffff).name() << endl;
15   cout << "0xffff0000 type: " << typeid(0xffff0000).name() << endl;
16   cout << "off_t type: " << typeid(off_t).name() << endl;
17   return 0;
18 }

51 descent@debian64:tmp$ ./a.out |c++filt 
52 0x7fffffff type:i
53 0xffff0000 type: j
54 off_t type: l


71 descent@debian64:tmp$ ./a.out |c++filt -t
72 0x7fffffff type:int
73 0xffff0000 type: unsigned int
74 off_t type: long


再來是一個很特別的數字, -2147483648。
測試環境是:
x86 32bit/linux
gcc 5.4.0
int: 32bit
longlong: 64bit

-2147483648 可以用 int 大小 (32bit) 裝下, 0x80000000 是其 16 進位表達式。
2147483648 卻要用 long long 大小 (64bit) 才能裝下, 0x0000000080000000 是其 16 進位表達式。

int.cpp
 1 #include <cstdio>
 2 #include <iostream>
 3 #include <typeinfo>
 4
 5 using namespace std;
 6
 7
 8 int main(int argc, char *argv[])
 9 {
10   int i = -2147483648;
11   cout <<  typeid(-2147483648).name() << endl;
12   printf("%d\n", -2147483648);
13 }

descent@debian64:ctrl_proc$ g++ -m32 -Wall -std=c++11 int.cpp -o int
int.cpp: In function ‘int main(int, char**)’:
int.cpp:12:29: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘long long int’ [-Wformat=]
   printf("%d\n", -2147483648);
                             ^
int.cpp:10:7: warning: unused variable ‘i’ [-Wunused-variable]
   int i = -2147483648;
       ^
descent@debian64:ctrl_proc$ ./int |c++filt -t
long long
-2147483648

-2147483648 type 是 long long, 而不是 int, 雖然 int 裝的下 -2147483648, 怎麼回事, 從 AST 來看這個 printf("%d\n", -2147483648); 運算式。

main |int |func |global
           |
       func_body
           |
         printf
         __|___
         |    |
        %d\n  -
              |
          2147483648

-2147483648 被分成 -, 2147483648, 而 2147483648 是 long long type, 做了 - 運算後, 依然是 long long, 所以 -2147483648 是 long long, 也就是 0xffffffff80000000, 而 %d 預期是 int type, 所以編譯器便發出 warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘long long int’ [-Wformat=] 警告。

有趣的是 int.cpp L10, int i = -2147483648; 卻不會發警告, int i 是可以存進 -2147483648, 真奇怪, -2147483648 的 type 是 long long, 所以會有被截掉的可能, 但是由於 -2147483648 可以裝進 int, 所以不用擔心會被截掉。

linux c 編程 一站式學習》作者很謹慎, 沒亂唬爛我。

c++ 是不是好用呢? 有一些高階語言的特性與低階語言的執行速度, 它是很了不起的語言。

ref:
Strange output of std::typeid::name()

2016年8月23日 星期二

ISO C trigraphs

a1.cpp
1 ??=include <stdio.h>
2 int main(int argc, char *argv[])
3 ??<
4   char ch[10]="abc";  
5   printf("trigraph test: %c\n", ch??(1??));
6   return 0;
7 ??>

g++ -ansi a1.cpp -o a1

Trigraph Sequences

TrigraphPunctuation Character
??=#
??([
??/\
??)]
??'^
??<{
??! |
??>}
??- ~

2016年6月21日 星期二

c 如何傳回 struct

c 如何 return 一個 struct 呢? 我很好奇, 來看看反組譯的程式碼。

ret_struct.c
 1 #include <stdio.h>
 2 
 3 typedef struct Data_
 4 {
 5   int a;
 6   int b;
 7   char ch;
 8 }Data;
 9 
10 Data ret_data()
11 {
12   Data d;
13 
14   d.a=100;
15   d.b=200;
16   d.ch = 'a';
17 
18   return d;
19 }
20 
21 int main(int argc, char *argv[])
22 {
23   Data da = ret_data(); 
24   da.a=101;
25   return 0;
26 }


ret_struct_c.asm
 1 
 2 ret_struct_c:     file format elf32-i386
 3 
 4 
 5 Disassembly of section .init:
 6 
 7 080483db <ret_data>:
 8   int b;
 9   char ch;
10 }Data;
11 
12 Data ret_data()
13 {
14  80483db: 55                    push   %ebp
15  80483dc: 89 e5                 mov    %esp,%ebp
16  80483de: 83 ec 10              sub    $0x10,%esp
17   Data d;
18 
19   d.a=100;
20  80483e1: c7 45 f4 64 00 00 00  movl   $0x64,-0xc(%ebp)
21   d.b=200;
22  80483e8: c7 45 f8 c8 00 00 00  movl   $0xc8,-0x8(%ebp)
23   d.ch = 'a';
24  80483ef: c6 45 fc 61           movb   $0x61,-0x4(%ebp)
25 
26   return d;
27  80483f3: 8b 45 08              mov    0x8(%ebp),%eax
28  80483f6: 8b 55 f4              mov    -0xc(%ebp),%edx
29  80483f9: 89 10                 mov    %edx,(%eax)
30  80483fb: 8b 55 f8              mov    -0x8(%ebp),%edx
31  80483fe: 89 50 04              mov    %edx,0x4(%eax)
32  8048401: 8b 55 fc              mov    -0x4(%ebp),%edx
33  8048404: 89 50 08              mov    %edx,0x8(%eax)
34 }
35  8048407: 8b 45 08              mov    0x8(%ebp),%eax
36  804840a: c9                    leave  
37  804840b: c2 04 00              ret    $0x4
38 
39 0804840e <main>:
40 
41 int main(int argc, char *argv[])
42 {
43  804840e: 55                    push   %ebp
44  804840f: 89 e5                 mov    %esp,%ebp
45  8048411: 83 ec 10              sub    $0x10,%esp
46   Data da = ret_data(); 
47  8048414: 8d 45 f4              lea    -0xc(%ebp),%eax
48  8048417: 50                    push   %eax
49  8048418: e8 be ff ff ff        call   80483db <ret_data>
50   da.a=101;
51  804841d: c7 45 f4 65 00 00 00  movl   $0x65,-0xc(%ebp)
52   return 0;
53  8048424: b8 00 00 00 00        mov    $0x0,%eax
54 }
55  8048429: c9                    leave  
56  804842a: c3                    ret    
57  804842b: 66 90                 xchg   %ax,%ax
58  804842d: 66 90                 xchg   %ax,%ax
59  804842f: 90                    nop

ret_struct_c.asm L27 0x8(%ebp) 就是 ret_struct.c L23 Data da, 透過 ret_struct_c.asm L47 ~ 49 可以看出, compiler 大概轉成 m_ret_struct.c

ret_struct_c.asm L47 -0xc(%ebp) 就是 da 的位址, push 給 ret_data() 就是傳給 ret_data(), 有趣的是我們的 ret_data 並沒有接受參數, 這類似於 return value optimization。

m_ret_struct.c
1 #include <stdio.h>
 2
 3 typedef struct Data_
 4 {
 5   int a;
 6   int b;
 7   char ch;
 8 }Data;
 9
10 void ret_data(Data *d)
11 {
12     Data data;

13     data.a=100;
14     data.b=200;
15     data.ch = 'a';

24     *d = data;
25 }
26
27 int main(int argc, char *argv[])
28 {
29   Data da;
30   ret_data(&da);
31   da.a=101;
32   return 0;
33 }
  

這樣也一篇, 當然阿!

2016年5月23日 星期一

很難用的 strncpy

  1. http://ideone.com/5K2BDn
  2. http://ideone.com/KokRhz

我是以 c++ 為主力, 幾乎都是用 std::string, c style string 讓我吃盡苦頭。list 1, 2 展示了 strncpy 的難用之處, 要非常的小心, strncpy 的行為有時候幫你加 '\0', 有時候又不會。

《strcpy ,strncpy ,strlcpy地用法》已經做了解釋就不再多言了, 要小心的是其建議寫法:

strncpy 的标准用法为:(手工写上 /0)

strncpy(path, src, sizeof(path) - 1);
path[sizeof(path) - 1] = '/0';
len = strlen(path);

path 得確定是個 array 才行, 若是指標那就要倒大楣了。

2016年4月22日 星期五

C 語言的 usual arithmetic conversion

程式在下面的文章哦!

ptt [問題] 十三誡之七的疑問》有一個這樣的問題。

a5.c
 1 #include <stdio.h>
 2 
 3 int main(int argc, char *argv[])
 4 {
 5   unsigned int a = 0;
 6   for(int i = 9 ; i >= a ; i--) 
 7   {  
 8     printf("i: %x\n", i);
 9   }
10     
11   return 0;
12 }

a5.c 執行結果是
i: 9
i: 8
i: 7
i: 6
i: 5
i: 4
i: 3
i: 2
i: 1
i: 0
i: ffffffff
i: fffffffe
i: fffffffd
i: fffffffc
i: fffffffb
i: fffffffa
i: fffffff9
i: fffffff8
i: fffffff7
i: fffffff6
i: fffffff5
i: fffffff4
...
...
... 

i 被當成 unsigned int 看待了, 這是怎麼回事?

tinlans Re: [問題] 十三誡之七的疑問》回覆的很詳細, 不過你可能被 c spec 的英文和複雜的規定搞得更亂了 (是少我是這樣), 《inux c 編程 一站式學習》 3.2. Usual Arithmetic Conversion 有簡單一點的說明。

a5.c 符合這點:
否则,如果一边是无符号数另一边是有符号数,无符号数的Rank不低于有符号数的Rank,则把有符号数转成另一边的无符号类型。例如unsigned long和int做算术运算时都转成unsigned long,unsigned long和long做算术运算时也都转成unsigned long。

其實就是 fig 1 的中文翻譯, 英文果然不是很好懂。

fig 1 n1570 c11 spec draft sual arithmetic conversion
寄件者 ??

所以 L6 int i 被轉成 unsigned int i 之後才和 a 做比較; i >= a 是以 unsigned int 在做比較, unsigned int 自然沒有小於零的數, 所以條件便一直成立。

這並不是 Integer Promotion, 而是 usual arithmetic conversion, 別搞錯了。

可见有符号和无符号整数的转换规则是十分复杂的,虽然这是有明确规定的,不属于阴暗角落,但为了程序的可读性不应该依赖这些规则来写代码。我讲这些规则,不是为了让你用,而是为了让你了解有符号数和无符号数混用会非常麻烦,从而避免触及这些规则,并且在程序出错时记得往这上面找原因。所以这些规则不需要牢记,但要知道有这么回事,以便在用到的时候能找到我书上的这一段。

感謝作者宋劲杉的苦口婆心。

如果你對《inux c 編程 一站式學習》這本書寫的有懷疑, 又看不懂 c spec, the c programming language 繁體中文版 A6.5 也介紹了 usual arithmetic conversions 的規則, 寫的不是很詳細, 不過對照程式應該可以看懂為什麼是符合該轉換規則。

fig 2 一樣在說明這件事。
fig 2 C语言程序设计现代方法第2版

C11: ISO/IEC 9899:2011 specification 似乎沒有想像中的恐怖, 大概 180 頁左右, 後面是標準程式庫。

ref:
C 語言的潛規則型態轉換

2016年3月2日 星期三

c 語言 - 比較 char c==0xff

不經一事不長一智

還真不知道怎麼寫這個標題, 從程式碼看結果。

d.cpp
 1 #include <stdio.h>
 2 
 3 int main(int argc, char *argv[])
 4 {
 5   char c = 0xff;
 6 
 7   printf("c: %hhx\n", c);
 8   if (c == 0xff)
 9   {
10     printf("yy\n");
11   }
12   else
13   {
14     printf("zz\n");
15   }
16   return 0;
17 }

d.cpp 應該要印出 yy 還是 zz 呢? 答案可能出乎你的意料之外, 回答 yy 或是 zz 的朋友恭喜你們都答對了。

編譯指令
g++ -m32 -o d.x86 d.cpp
arm-linux-gnueabihf-g++ -o d.rpi2 d.cpp


x86 測試結果
descent@debian64:tmp$ g++ -m32 -o d.x86 d.cpp
descent@debian64:tmp$ ./d.x86
c: ff
zz


rpi2 測試結果
raspberrypi:~# g++ -o d.arm d.cpp
raspberrypi:~# ./d.arm
c: ff
yy

怎麼辦? 把 char c = 0xff; 改成 unsigned char c = 0xff; 就可以得到相同結果 yy; 而把 char c = 0xff; 改成 signed char c = 0xff 則會得到相同結果 zz。

以下的 gcc option 也可以做到類似的效果。

man gcc
-fsigned-char
Let the type "char" be signed, like "signed char".

Note that this is equivalent to -fno-unsigned-char, which is the negative form of -funsigned-char. Likewise, the option -fno-signed-char is equivalent to -funsigned-char.

看來還是把 char 的 signed/unsigned 指定好比較保險。

arm 版本的 gcc char 是 unsigned char; x86 版本的 gcc char 是 signed char。

2015年9月21日 星期一

在 c 中使用類似 c++ 的 exception handle 機制

Exceptions in C with Longjmp and Setjmp 這篇文章已經把標題解釋完了, 要理解該篇文章, 需要瞭解 setjmp/longjmp 的使用方式。

那 ... 我要寫些什麼呢?

文章的 macro 可能會讓你不好理解 (我也是), 所以我列出了展開 macro 的程式碼, ex.c L25 ~ 59 的 f1 就是這樣的展現。很正常的 c 程式碼, 一點也不令人害怕, 但是一看 f2, 就會覺得神奇, 而 f1, f2 是一樣的東西, 只不過 f2 用 macro 包裝起來。

另外那個 FINALLY 的版本很神奇的把 while(1) 放到 case 0 裡頭, 真是厲害。

使用類似 exception 的錯誤處理有什麼好處呢? 你是不是覺得每次都要去檢查 malloc 的傳回值很麻煩, 希望有錯的時候丟出 exception 就好了, L13 的 mymalloc 就是在模擬這樣的行為, 這樣就不用每次都去檢查 malloc 是不是傳回 NULL 了。

ex.c
 1 // ref: http://www.di.unipi.it/~nids/docs/longjump_try_trow_catch.html
 2 #include <stdio.h>
 3 #include <setjmp.h>
 4 #include <stdlib.h>
 5 
 6 #define TRY do { switch((ex_code = setjmp(ex_buf__))) { case 0:
 7 #define CATCH(x) break; case x : 
 8 #define ETRY break; } } while(0);
 9 #define THROW(x) longjmp(ex_buf__, x)
10 
11 jmp_buf ex_buf__; 
12 
13 void *mymalloc(size_t size)
14 {
15   static int t;
16   void *mem = malloc(size);
17   if (t >= 2)
18     mem = NULL;
19   if (mem == NULL)
20     longjmp(ex_buf__, 1); // THROW
21   ++t;
22   return mem;
23 }
24 
25 int f1()
26 {
27   int ex_code = 0;
28   do
29   { 
30     switch((ex_code = setjmp(ex_buf__)))
31     { 
32       case 0: // TRY
33       {
34         printf("In Try Statement\n");
35         
36         char *mem = (char *)mymalloc(32);
37         mem = (char *)mymalloc(32);
38         mem = (char *)mymalloc(32);
39         printf("I do not appear\n");
40       }
41 
42       break; 
43       case 1 : // CATCH(x)
44       {
45         printf("mem alloc fail! %d\n", ex_code);
46       }
47 
48       break; 
49       case 5 : // CATCH(x)
50       {
51         printf("Got Exception! %d\n", ex_code);
52       }
53 
54       break; 
55     }
56   } while(0); // ETRY
57 
58   return 0;
59 }
60 
61 int f2()
62 {
63   int ex_code = 0;
64       TRY
65       {
66         printf("In Try Statement\n");
67         
68         THROW(5);
69         printf("I do not appear\n");
70       }
71       CATCH(1)
72       {
73         printf("Got Exception! %d\n", ex_code);
74       }
75       CATCH(5)
76       {
77         printf("Got Exception! %d\n", ex_code);
78       }
79       ETRY
80 
81   return 0;
82 }
83 
84 int main(int argc, char *argv[])
85 {
86   f1(); 
87   f2(); 
88   return 0;
89 }


z.c L12 的 while(1) 真是神奇的用法, 我都不知道要怎麼排版了。

z.c
 1 #include <stdio.h>
 2 #include <setjmp.h>
 3 
 4 int main(int argc, char **argv)
 5 {
 6   do
 7   {
 8     jmp_buf ex_buf__;
 9     switch (_setjmp(ex_buf__))
10     {
11       case 0:
12       while (1)
13       {
14         {
15           printf("In Try Statement\n");
16           longjmp(ex_buf__, (2));
17           printf("I do not appear\n");
18         }
19         break;
20         case (1):
21         {
22           printf("Got Foo!\n");
23         }
24         break;
25         case (2):
26         {
27           printf("Got Bar!\n");
28         }
29         break;
30         case (3):
31         {
32           printf("Got Baz!\n");
33         }
34         break;
35       } // end while(1)
36       default:
37       {
38         {
39           printf("...et in arcadia Ego\n");
40         }
41       }
42     }
43   }
44   while (0);
45 
46   return 0;
47 }

獲得此技能後, 我稍微改寫了 bare metal programming for stm32f4 - discovery: using c++ std::vector 的 class my_allocator, 讓它在要不到記憶體時, 丟出 exception, 其實就只是發動 longjmp 而已, 真的沒什麼了不起。

result in qemu stm32-p103
  1 descent@debian64:myvec$ ~/git/qemu_stm32/arm-softmmu/qemu-system-arm -M stm32-p103 -kernel myvec.bin  -nographic
  2 STM32_UART: UART1 clock is set to 0 Hz.
  3 STM32_UART: UART1 BRR set to 0.
  4 STM32_UART: UART1 Baud is set to 0 bits per sec.
  5 STM32_UART: UART2 clock is set to 0 Hz.
  6 STM32_UART: UART2 BRR set to 0.
  7 STM32_UART: UART2 Baud is set to 0 bits per sec.
  8 STM32_UART: UART3 clock is set to 0 Hz.
  9 STM32_UART: UART3 BRR set to 0.
 10 STM32_UART: UART3 Baud is set to 0 bits per sec.
 11 STM32_UART: UART4 clock is set to 0 Hz.
 12 STM32_UART: UART4 BRR set to 0.
 13 STM32_UART: UART4 Baud is set to 0 bits per sec.
 14 STM32_UART: UART5 clock is set to 0 Hz.
 15 STM32_UART: UART5 BRR set to 0.
 16 STM32_UART: UART5 Baud is set to 0 bits per sec.
 17 STM32_UART: UART5 clock is set to 0 Hz.
 18 STM32_UART: UART5 BRR set to 0.
 19 STM32_UART: UART5 Baud is set to 0 bits per sec.
 20 STM32_UART: UART4 clock is set to 0 Hz.
 21 STM32_UART: UART4 BRR set to 0.
 22 STM32_UART: UART4 Baud is set to 0 bits per sec.
 23 STM32_UART: UART3 clock is set to 0 Hz.
 24 STM32_UART: UART3 BRR set to 0.
 25 STM32_UART: UART3 Baud is set to 0 bits per sec.
 26 STM32_UART: UART2 clock is set to 0 Hz.
 27 STM32_UART: UART2 BRR set to 0.
 28 STM32_UART: UART2 Baud is set to 0 bits per sec.
 29 STM32_UART: UART1 clock is set to 0 Hz.
 30 STM32_UART: UART1 BRR set to 0.
 31 STM32_UART: UART1 Baud is set to 0 bits per sec.
 32 LED Off
 33 CLKTREE: HSI Output Change (SrcClk:None InFreq:8000000 OutFreq:8000000 Mul:1 Div:1 Enabled:1)
 34 CLKTREE: HSI/2 Output Change (SrcClk:HSI InFreq:8000000 OutFreq:4000000 Mul:1 Div:2 Enabled:1)
 35 CLKTREE: SYSCLK Output Change (SrcClk:HSI InFreq:8000000 OutFreq:8000000 Mul:1 Div:1 Enabled:1)
 36 CLKTREE: HCLK Output Change (SrcClk:SYSCLK InFreq:8000000 OutFreq:8000000 Mul:1 Div:1 Enabled:1)
 37 STM32_RCC: Cortex SYSTICK frequency set to 8000000 Hz (scale set to 125).
 38 STM32_RCC: Cortex SYSTICK ext ref frequency set to 1000000 Hz (scale set to 1000).
 39 CLKTREE: PCLK1 Output Change (SrcClk:HCLK InFreq:8000000 OutFreq:8000000 Mul:1 Div:1 Enabled:1)
 40 CLKTREE: PCLK2 Output Change (SrcClk:HCLK InFreq:8000000 OutFreq:8000000 Mul:1 Div:1 Enabled:1)
 41 CLKTREE: HSE Output Change (SrcClk:None InFreq:8000000 OutFreq:8000000 Mul:1 Div:1 Enabled:1)
 42 CLKTREE: HSE/2 Output Change (SrcClk:HSE InFreq:8000000 OutFreq:4000000 Mul:1 Div:2 Enabled:1)
 43 CLKTREE: PLLXTPRE Output Change (SrcClk:HSE InFreq:8000000 OutFreq:8000000 Mul:1 Div:1 Enabled:1)
 44 CLKTREE: PCLK1 Output Change (SrcClk:HCLK InFreq:8000000 OutFreq:4000000 Mul:1 Div:2 Enabled:1)
 45 CLKTREE: PLLCLK Output Change (SrcClk:PLLXTPRE InFreq:8000000 OutFreq:72000000 Mul:9 Div:1 Enabled:1)
 46 CLKTREE: SYSCLK Output Change (SrcClk:PLLCLK InFreq:72000000 OutFreq:72000000 Mul:1 Div:1 Enabled:1)
 47 CLKTREE: HCLK Output Change (SrcClk:SYSCLK InFreq:72000000 OutFreq:72000000 Mul:1 Div:1 Enabled:1)
 48 STM32_RCC: Cortex SYSTICK frequency set to 72000000 Hz (scale set to 13).
 49 STM32_RCC: Cortex SYSTICK ext ref frequency set to 9000000 Hz (scale set to 111).
 50 CLKTREE: PCLK1 Output Change (SrcClk:HCLK InFreq:72000000 OutFreq:36000000 Mul:1 Div:2 Enabled:1)
 51 CLKTREE: PCLK2 Output Change (SrcClk:HCLK InFreq:72000000 OutFreq:72000000 Mul:1 Div:1 Enabled:1)
 52 CLKTREE: GPIOA Output Change (SrcClk:PCLK2 InFreq:72000000 OutFreq:72000000 Mul:1 Div:1 Enabled:1)
 53 CLKTREE: AFIO Output Change (SrcClk:PCLK2 InFreq:72000000 OutFreq:72000000 Mul:1 Div:1 Enabled:1)
 54 CLKTREE: UART2 Output Change (SrcClk:PCLK1 InFreq:36000000 OutFreq:36000000 Mul:1 Div:1 Enabled:1)
 55 STM32_UART: UART2 clock is set to 36000000 Hz.
 56 STM32_UART: UART2 BRR set to 0.
 57 STM32_UART: UART2 Baud is set to 0 bits per sec.
 58 STM32_UART: UART2 clock is set to 36000000 Hz.
 59 STM32_UART: UART2 BRR set to 3750.
 60 STM32_UART: UART2 Baud is set to 9600 bits per sec.
 61 
 62 throw ex test
 63 mymalloc: 1 byte(s)
 64 used my_allocator to allocate at address x n: 536883612 sizeof(T): 1
 65 mymalloc: 2 byte(s)
 66 used my_allocator to allocate at address x n: 536883740 sizeof(T): 2
 67 myfree: p
 68 index: 0
 69 size: 1
 70 used my_allocator to free address x
 71 mymalloc: 4 byte(s)
 72 used my_allocator to allocate at address x n: 536883868 sizeof(T): 4
 73 myfree: p
 74 index: 1
 75 size: 1
 76 used my_allocator to free address x
 77 mymalloc: 8 byte(s)
 78 used my_allocator to allocate at address x n: 536883996 sizeof(T): 8
 79 myfree: p
 80 index: 2
 81 size: 1
 82 used my_allocator to free address x
 83 mymalloc: 16 byte(s)
 84 used my_allocator to allocate at address x n: 536884124 sizeof(T): 16
 85 myfree: p
 86 index: 3
 87 size: 1
 88 used my_allocator to free address x
 89 mymalloc: 32 byte(s)
 90 used my_allocator to allocate at address x n: 536884252 sizeof(T): 32
 91 myfree: p
 92 index: 4
 93 size: 1
 94 used my_allocator to free address x
 95 mymalloc: 64 byte(s)
 96 used my_allocator to allocate at address x n: 536884380 sizeof(T): 64
 97 myfree: p
 98 index: 5
 99 size: 1
100 used my_allocator to free address x
101 mymalloc: 128 byte(s)
102 used my_allocator to allocate at address x n: 536884508 sizeof(T): 128
103 myfree: p
104 index: 6
105 size: 1
106 used my_allocator to free address x
107 mymalloc: 256 byte(s)
108 EXCEED_MEMAREA
109 not enough: free_index: 8, size: 2
110 new_index: 0
111 used my_allocator to allocate at address x n: 536883612 sizeof(T): 256
112 myfree: p
113 index: 7
114 size: 1
115 used my_allocator to free address x
116 mymalloc: 512 byte(s)
117 used my_allocator to allocate at address x n: 536883868 sizeof(T): 512
118 myfree: p
119 index: 0
120 size: 2
121 used my_allocator to free address x
122 mymalloc: 1024 byte(s)
123 EXCEED_MEMAREA
124 not enough: free_index: 6, size: 8
125 new_index: 0
126 used my_allocator to allocate at address x n: 536883612 sizeof(T): 1024
127 myfree: p
128 index: 2
129 size: 1
130 used my_allocator to free address x
131 mymalloc: 2048 byte(s)
132 EXCEED_MEMAREA
133 not enough: free_index: 8, size: 16
134 EXCEED_MEMAREA
135 EXCEED_MEMAREA
136 cannot alloc memory
137 
138 got no free mem

vec.cpp
  1 #include "mem.h"
  2 #include "k_stdio.h"
  3 #include "my_setjmp.h"
  4 
  5 #define setjmp my_setjmp
  6 #define longjmp my_longjmp
  7 
  8 #define TRY do { switch((ex_code = setjmp(ex_buf__))) { case 0:
  9 #define CATCH(x) break; case x : 
 10 #define ETRY break; } } while(0);
 11 #define THROW(x) longjmp(ex_buf__, x)
 12 
 13 #define NOFREE_MEM 5
 14 
 15 jmp_buf ex_buf__;
 16 
 17 using namespace DS; 
 18 using namespace std;
 19 
 20 #include <vector>
 21 #include <string>
 22 #include <map>
 23 
 24 void *dso_handle_;
 25 void *__dso_handle;
 26 
 27 extern "C" void _exit()
 28 {
 29 }
 30 
 31 char brk_area[10240];
 32 
 33 extern "C" char *_sbrk(char *increment)
 34 {
 35   char *ptr = brk_area;
 36   return ptr;
 37 }
 38 
 39 extern "C"
 40 int _kill(int a, int b)
 41 {
 42   return a;
 43 }
 44 
 45 extern "C"
 46 int _getpid()
 47 {
 48   int i;
 49   return i;
 50 }
 51 
 52 extern "C"
 53 int _write(int fd, const void *buf, int count)
 54 {
 55 }
 56 
 57 extern "C"
 58 int open(const char *pathname, int flags, int mode)
 59 {
 60 }
 61 
 62 extern "C"
 63 int _open(const char *pathname, int flags, int mode)
 64 {
 65 }
 66 
 67 
 68 extern "C"
 69 int _isatty(int fd)
 70 {
 71 }
 72 
 73 
 74 extern "C"
 75 int _close(int fd)
 76 {
 77 }
 78 
 79 extern "C"
 80 int _fstat(int fd, struct stat *buf)
 81 {
 82 }
 83 
 84 extern "C"
 85 int _read(int fd, void *buf, int count)
 86 {
 87 }
 88 
 89 extern "C"
 90 int _lseek(int fd, int offset, int whence)
 91 {
 92 }
 93 
 94 static char memarea[10240];
 95 
 96 void out_of_mem()
 97 {
 98   while(1);
 99 }
100 
101 template <class T>
102 class my_allocator
103 {
104 public:
105   typedef int    size_type;
106   typedef int difference_type;
107   typedef T*        pointer;
108   typedef const T*  const_pointer;
109   typedef T&        reference;
110   typedef const T&  const_reference;
111   typedef T         value_type;
112 
113   my_allocator() {}
114   my_allocator(const my_allocator&) {}
115 
116 
117 
118   pointer   allocate(size_type n, const void * = 0) {
119               T* t = (T*) mymalloc(n * sizeof(T));
120               if (t==0)
121               {
122                 printf("cannot alloc memory\n");
123                 //throw std::bad_alloc();
124                 THROW(NOFREE_MEM);
125                 //out_of_mem();
126                 //exit(-1);
127               }
128               printf("used my_allocator to allocate at address %x n: %d sizeof(T): %d", (int)t, n, sizeof(T));
129               printf("\r\n");
130 
131               return t;
132             }
133   
134   void      deallocate(void* p, size_type) {
135               if (p) {
136                 myfree(p);
137                 printf("used my_allocator to free address %x", (int)p);
138                 printf("\r\n");
139               } 
140             }
141 
142   pointer           address(reference x) const { return &x; }
143   const_pointer     address(const_reference x) const { return &x; }
144   my_allocator<T>&  operator=(const my_allocator&) { return *this; }
145   void              construct(pointer p, const T& val) 
146                     { new ((T*) p) T(val); }
147   void              destroy(pointer p) { p->~T(); }
148 
149   size_type         max_size() const { return int(-1); }
150 
151   template <class U>
152   struct rebind { typedef my_allocator<U> other; };
153 
154   template <class U>
155   my_allocator(const my_allocator<U>&) {}
156 
157   template <class U>
158   my_allocator& operator=(const my_allocator<U>&) { return *this; }
159 };
160 
178 
179 void vec_test_eh(void)
180 {
181 #if 1
182   int ex_code = 0;
183   printf("\r\nthrow ex test\r\n");
184 
185   TRY
186   {
187     std::vector<char, my_allocator<char> > vec;
188     for (int i=0 ; i < 2000 ; ++i)
189     {
190       vec.push_back(i);
191     }
192   }
193   CATCH(NOFREE_MEM)
194   {
195     printf("\r\ngot no free mem\r\n");
196   }
197   ETRY
198   while(1);
199 #endif
200 }
201 

順道題一下 g++5, g++4 的小差異, 還有 float 是 hard/soft 對編譯程式碼的影響。

g++ 5 產生的 text 部份很大, 我猜測是 g++ 搭配的 c++ library 其 vector 程式碼變大了, 這已經影響到我能用的空間了。

原本是

MEMORY
{
 FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 128K
 RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}

改成

MEMORY
{
 FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 1280K
 RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 1280K
}

才能產生出 elf 執行檔。

stm32 的 flash 和 ram 很小, 塞不下了。

descent@debian64:myvec$ size myvec.elf.gcc51.fp_hard myvec.elf.gcc51.fp_soft myvec.elf.gcc48 
   text    data     bss     dec     hex filename
 475426    1501   91889  568816   8adf0 myvec.elf.gcc51.fp_hard (64 bit arm gcc 51)
 474820    1501   91889  568210   8ab92 myvec.elf.gcc51.fp_soft (32 bit arm gcc 51)
 114281    2300   89961  206542   326ce myvec.elf.gcc48

不過這只是測試用的, 我從來就沒想過要把 vector 用到自己的 os 上, 有需要我會自己寫一個 vector, 又不難, 網路上就有得抄。

一開始用的 g++ 51 是 hard fp, 結果 -mfloat-abi=soft 編譯有問題, 改成 -mfloat-abi=hard 才能編過。後來重新建立一個 g++ 51 是 soft fp 的版本, -mfloat-abi=soft 就可以編過了。

2015年7月26日 星期日

c/c++ 語言的 前++ 與 後++

int i=5;
i = i++;
這個惱人的問題這篇有解釋, 是未定義行為。有些面試題目會考這個, 實在是折磨人。

我想知道前++ 與後++ 到底有什麼差別, 組合語言之前沒有祕密, 好久不見的組語再次出動了, 這次的很簡單, 我還補上圖示, 沒問題的。

fig1

後++ L22 有個 lea 指令, 這個有點難懂, 請參考以下連結。
所以在 x=i++ 後, x=5, i=6。

lea:
比较汇编指令 LEA 和 MOV
有意思的lea指令

fig1 是我分解反組譯後的圖示, 1 ~ 4 的步驟分別說明這個後++ 的行為。

後++
 1 #include <stdio.h>
 2 
 3 int main(int argc, char *argv[])
 4 {
 5   int x=1, i=5;
 6   x = i++;
 7   printf("x: %d, i: %d\n", x, i);
 8   return 0;
 9 }
10 
11 080483fb <main>:
12  80483fb:       8d 4c 24 04             lea    0x4(%esp),%ecx
13  80483ff:       83 e4 f0                and    $0xfffffff0,%esp
14  8048402:       ff 71 fc                pushl  -0x4(%ecx)
15  8048405:       55                      push   %ebp
16  8048406:       89 e5                   mov    %esp,%ebp
17  8048408:       51                      push   %ecx
18  8048409:       83 ec 14                sub    $0x14,%esp
19  804840c:       c7 45 f4 01 00 00 00    movl   $0x1,-0xc(%ebp)
20  8048413:       c7 45 f0 05 00 00 00    movl   $0x5,-0x10(%ebp)
21  804841a:       8b 45 f0                mov    -0x10(%ebp),%eax
22  804841d:       8d 50 01                lea    0x1(%eax),%edx
23  8048420:       89 55 f0                mov    %edx,-0x10(%ebp)
24  8048423:       89 45 f4                mov    %eax,-0xc(%ebp)
25  8048426:       83 ec 04                sub    $0x4,%esp
26  8048429:       ff 75 f0                pushl  -0x10(%ebp)
27  804842c:       ff 75 f4                pushl  -0xc(%ebp)
28  804842f:       68 e0 84 04 08          push   $0x80484e0
29  8048434:       e8 97 fe ff ff          call   80482d0 <printf@plt>
30  8048439:       83 c4 10                add    $0x10,%esp
31  804843c:       b8 00 00 00 00          mov    $0x0,%eax
32  8048441:       8b 4d fc                mov    -0x4(%ebp),%ecx
33  8048444:       c9                      leave
34  8048445:       8d 61 fc                lea    -0x4(%ecx),%esp
35  8048448:       c3                      ret  
36 
37 

fig2

那如果是 i = i++ 會怎樣, 依樣畫葫蘆, 把 x (-0xc) 用 i 代替, 而改到 -0xc, 就等於改動 -0x10, 因為現在把這兩個位址看成是同一個, 這就是 fig2 的動作, 以這個反組譯來說, i 最後會得到 5。在步驟 2 的時候, i = 6, 但是到了步驟 4, i 變成了 5。所以若是步驟 2, 4 顛倒的話, 最後結果就是 i=6。



 前++ 就單純多了, 但我不知道這是不是標準行為。




前++
38 #include <stdio.h> 
39  
40 int main(int argc, char *argv[]) 
41 { 
42   int x=1, i=5; 
43   x = ++i; 
44   printf("x: %d, i: %d\n", x, i); 
45   return 0;  
4647   
48  80483fb:       8d 4c 24 04             lea    0x4(%esp),%ecx  
49  80483ff:       83 e4 f0                and    $0xfffffff0,%esp  
50  8048402:       ff 71 fc                pushl  -0x4(%ecx)  
51  8048405:       55                      push   %ebp  
52  8048406:       89 e5                   mov    %esp,%ebp  
53  8048408:       51                      push   %ecx  
54  8048409:       83 ec 14                sub    $0x14,%esp  
55  804840c:       c7 45 f4 01 00 00 00    movl   $0x1,-0xc(%ebp)  
56  8048413:       c7 45 f0 05 00 00 00    movl   $0x5,-0x10(%ebp)  
57  804841a:       83 45 f0 01             addl   $0x1,-0x10(%ebp)  
58  804841e:       8b 45 f0                mov    -0x10(%ebp),%eax  
59  8048421:       89 45 f4                mov    %eax,-0xc(%ebp)  
60  8048424:       83 ec 04                sub    $0x4,%esp  
61  8048427:       ff 75 f0                pushl  -0x10(%ebp)  
62  804842a:       ff 75 f4                pushl  -0xc(%ebp)  
63  804842d:       68 e0 84 04 08          push   $0x80484e0  
64  8048432:       e8 99 fe ff ff          call   80482d0 <printf@plt>  
65  8048437:       83 c4 10                add    $0x10,%esp  
66  804843a:       b8 00 00 00 00          mov    $0x0,%eax  
67  804843f:       8b 4d fc                mov    -0x4(%ebp),%ecx  
68  8048442:       c9                      leave  
69  8048443:       8d 61 fc                lea    -0x4(%ecx),%esp  
70  8048446:       c3                      ret     





所以後++ 需要多一個東西來暫存中間結果, 這就是為什麼 c++ 建議用前++, 而少用後++, 這樣就不需要多一個東西來暫存, 速度自然也快一些。

不過編出來的執行檔大小都一樣, 我不知道速度是不是前++ 會快些, 不知道還有什麼魔法。

2015年7月13日 星期一

有著 const 修飾的 c pointer

經過 os kernel 修煉之後, 我本來以為對指標已達「略懂」的境界, 不過看了《[問題] 為什麼兩個 pointer 不能轉 const》 (該篇的討論有答案) 之後, 我才知道還有不足, 這次不是記憶體佈局的問題, 是在 compiler 這個階段。

有的人說指標很簡單, 我不知道他是真的把指標搞懂了還是誤會了指標的困難度, 指標的複雜度有兩方面: run time 和 compiler time。而這個 const 是在 compiler time, 怎麼把這個 const 指標寫對是很困難的, 而 c 和 c++ 又有不同的觀點。

const int *  *  * p1;
      int *  *  * p2;
p1=p2;   

上述的程式碼無法正常 compile, 我很驚訝,

const int * p1;
      int *p2;
p1=p2;   

這樣就可以, 為什麼多了幾顆星星就不行, 許多人已經提出答案了, 就不重複說明了, 參考以下連結。

ref:
  1. const 的使用 (4) - 基礎測驗解答篇
  2. Re: [問題] 為什麼兩個 pointer 不能轉 const
a.cpp, b.cpp 分別用來觀察在 dereference 時, 取到的型別是什麼?

a.cpp
 1 #include <stdio.h>
 2 #include <typeinfo>
 3 #include <iostream>
 4 
 5 using namespace std;
 6 
 7 void func(const char *const * tokens, int len)
 8 {
 9   for (int i=0 ; i < len ; ++i)
10     cout << *(tokens+i) << endl;
11 }
12 
13 int main(int argc, char *argv[])
14 {
15   char s1[]="abc";
16   char s2[]="xyz";
17   char *tokens[3] = {s1, s2};
18   func(tokens, 2);
19 
20   int *  *  const* p1;
21 
22   cout << "p1 type: " << typeid(p1).name() << endl;
23   cout << "*p1 type: " << typeid(*p1).name() << endl;
24   cout << "**p1 type: " << typeid(**p1).name() << endl;
25   cout << "***p1 type: " << typeid(***p1).name() << endl;
26   return 0;
27 }



result1
p1 type: PKPPi
*p1 type: PPi
**p1 type: Pi
***p1 type: i

PK 是一個 const pointer, P 則是 non-const pointer。

b.cpp
 1 #include <stdio.h>
 2 #include <typeinfo>
 3 #include <iostream>
 4 
 5 using namespace std;
 6 
 7 void func(const char *const * tokens, int len)
 8 {
 9   for (int i=0 ; i < len ; ++i)
10     cout << *(tokens+i) << endl;
11 }
12 
13 int main(int argc, char *argv[])
14 {
15   char s1[]="abc";
16   char s2[]="xyz";
17   char *tokens[3] = {s1, s2};
18   func(tokens, 2);
19 
20   int const*  *  * p1;
21 
22   cout << "p1 type: " << typeid(p1).name() << endl;
23   cout << "*p1 type: " << typeid(*p1).name() << endl;
24   cout << "**p1 type: " << typeid(**p1).name() << endl;
25   cout << "***p1 type: " << typeid(***p1).name() << endl;
26   return 0;
27 }


result2
p1 type: PPPKi
*p1 type: PPKi
**p1 type: PKi
***p1 type: i


手工打造的示意圖
寄件者 ??

再來便是如何正確傳入 const pointer 的問題, 你很想這麼做, 但編譯器老是發出錯誤或是警告訊息嗎? 所以你乾脆不使用 const 了, 我也曾經遇到類似的麻煩事, c.cpp 把幾種情況列出來, c.cpp 無法通過編譯, 只是列出語法, 使用了正確的宣告後, 編譯器終於安靜下來了, 用 c++ compiler 才會安靜, 用 c compiler 一樣會發出警告。請參考 ref 2 的討論。

c.cpp
 1 #include <stdio.h>
 2 #include <typeinfo>
 3 #include <iostream>
 4 
 5 using namespace std;
43   char s1[]="abc";
44   char s2[]="xyz";
45   char s3[]="lmk";
 6 
11 
12 //const char * const tokens[]={s1, s2};
13 void func(const char * const* tokens, int len)
14 {
15   for (int i=0 ; i < len ; ++i)
16     cout << *(tokens+i) << endl;
17 }
18 
19 //const char * tokens[]={s1, s2};
20 void func(const char * * tokens, int len)
21 {
22   for (int i=0 ; i < len ; ++i)
23     cout << *(tokens+i) << endl;
24 }
25 
26 //char * tokens[]={s1, s2};
27 void func(const char * const* tokens, int len)
28 {
29   for (int i=0 ; i < len ; ++i)
30     cout << *(tokens+i) << endl;
31 }
32 
33 //char * tokens[]={s1, s2};
34 void func(char * * tokens, int len)
35 {
36   for (int i=0 ; i < len ; ++i)
37     cout << *(tokens+i) << endl;
38 }

ref:

2013年10月26日 星期六

process switch for stm32f4discovery (0) - 小試身手

x86 process switch 系列後, 我嘗試實作 arm cm3 的 process switch, 基本原理是一樣的, 不過 stack 處理可讓我傷透腦筋, 也才有 x86 process switch implementation (1.5) - save/restore in real mode 這篇, 算是意外的收穫, 腦筋只要動, 還是想得出方法。

不過這篇是在還沒想到之前寫的, 完成後固然開心, 但這不是在 arm cm3 的作法, 實際上的作法有點不同 (類似 1.5 那篇)。但是這方法很容易理解, 還是值得說明一下, 來看看沒有保存 stack 的作法。

source code: https://github.com/descent/stm32f4_prog
git commit b78cb69ce5b61421d0ea865939a983930e1f3fea

process.S
  1 # test svc software interrupt
  2 # ref : http://tw.m.wretch.yahoo.com/blog/DreamYeh/888788
  3 # comment: #, @
  4 
  5 .equ STACK_TOP, 0x20000800
  6 .text
  7 .global _start
  8 .code 16
  9 .syntax unified
 10 _start:
 11   .word STACK_TOP, start 
 12   .type start, function @ let lsb to 1
 13 
 14   .word int_isr+1
 15   .word int_isr+1
 16   .word int_isr+1
 17   .word int_isr+1
 18   .word int_isr+1
 19   .word int_isr+1
 20   .word int_isr+1
 21   .word int_isr+1
 22   .word int_isr+1
 23   .word int_isr+1 @ svc isr
 24   .word int_isr+1
 25   .word int_isr+1
 26   .word int_isr+1
 27   .word int_isr+1
 28   .word int_isr+1
 29   .word int_isr+1
 30   .word int_isr+1
 31 
 32 start:
 33   ldr r0, =stack_frame_a
 34 
 35   ldr r1,=#0xfffffff9
 36   str r1, [r0, #20] @ setup lr
 37 
 38   adr r1, proc_a
 39   str r1, [r0, #24] @ setup pc
 40   mov r1, #0x1000000
 41   str r1, [r0, #28] @ setup psr
 42 
 43   @@@@@@@@@@@@@@@@@@@@@
 44 
 45   ldr r0, =stack_frame_b
 46 
 47   ldr r1,=#0xfffffff9
 48   str r1, [r0, #20] @ setup lr
 49 
 50   ldr r1, =proc_b
 51   str r1, [r0, #24] @ setup pc
 52   mov r1, #0x1000000
 53   str r1, [r0, #28] @ setup psr
 54 
 55   @@@@@@@@@@@@@@@@@@@@@
 56   ldr r5, =cur_proc
 57   mov r1, #0
 58   str r1, [r5]
 59 
 60   svc 0
 61 
 62 deadloop:
 63   b deadloop
 64 
 65 .type proc_a, function @ let lsb to 1
 66 proc_a:
 67   movs r0, #1
 68   svc 0
 69   nop
 70   b proc_a
 71 
 72 .type proc_b, function @ let lsb to 1
 73 proc_b:
 74   movs r1, #2
 75   nop
 76   svc 0
 77   b proc_b
 78 
 79 int_isr:
 80   movs r5, #5
 81   ldr r5, =cur_proc
 82   ldr r0, [r5]
 83   cmp r0, #0
 84   ittee eq
 85   ldreq sp,=#stack_frame_a
 86   moveq r1, #1
 87   ldrne sp,=#stack_frame_b
 88   movne r1, #0
 89 
 90   str r1, [r5]
 91   bx lr
 92 .data
 93 
 94 stack_frame_a:
 95 .word 9 @r0
 96 .word 8 @r1
 97 .word 7 @r2
 98 .word 6 @r3
 99 .word 5 @r12
100 .word 4 @lr 
101 .word 3 @pc @ proc_a
102 .word 2 @psr
103 
104 stack_frame_b:
105 .word 1 @r0
106 .word 2 @r1
107 .word 3 @r2
108 .word 4 @r3
109 .word 5 @r12
110 .word 6 @lr
111 .word 7 @pc
112 .word 8 @psr
113 
114 cur_proc:
115 .word 1

一樣有兩個 stack frame (L94 ~ L102, L104 ~ L112), 保存 r0~r3, r12, lr, pc, psr, L33 ~ L53 把 lr, pc, psr, 設定好即可。在 int_isr 只要輪由切換 stack_frame_a, stack_frame_b 的位址到 sp 即可, 如何, 很簡單吧?

在 int_isr return 時, 會根據 stack frame pc 這欄位而跳到那裡執行。執行 svc 0 時, 會把下一個位址紀錄在 stack frame pc 這欄位。這是基本原理。

中斷時 stack 變化請參考 arm cortex-m3: 嵌入式系統設計入門 p9-2。lr 的值可參考 arm cortex-m3: 嵌入式系統設計入門 p9-7。

疑!你沒打算說明阿? 當然阿! 書上有的東西我幹嘛再打一次。