C

閱讀 C 語言規格書的重要性之序章 : 一元運算元的案例分析

案例一

引述 C 語言規格書中的三點來解釋為何以下程式碼能正常運作

int main() {
   return (********puts)("Hello");
}

[1] 6.5.3.2-4 The unary * operator denotes indirection. If the operand pointers to a function, the result is a function designator;

[2] 6.3.2.1-4 A function designator is an expression that has function type.

[3] * is Right associative operator

由於 puts 的 function signature 是 int puts(const char *s),所以, 每一次經過 * operator 的運算後得到的結果是仍然是 int. 因此, * 的數目不會影響結果。最後 return 的值是根據 s 的長度加上 ‘\n’。而這個例子 return 給 entry point 的值是 6.


案例二

以下的表達式 [a] 和 [b] 是否等價?

[a] (**printf)("...");
[b] (&printf)("...");

引述 C語言規格書:

[0] 6.3.2.1-4 A function designator is an expression that has function type.
[1] 6.5.3.2-1 The operand of the unary &; operator shall be a function designator
[2] 6.5.3.2-3 The unary &; operator yields the address of its operand.
[3] 6.5.3.2-4 The unary * operator denotes indirection. If the operand points to a function, the result is a function designator.

假設 printf 的 function signature 是 int printf (const char *, …);
那麼每一次經過 * operator 和 & operator 的運算後得到的結果是仍然是 int ,所以這兩個 expression 是等價的

[a] (**printf)(“..") -> (* int printf)(“..") -> (int printf)(“..")
[b] (&printf)(“…"); -> int printf (“…");

如果把 (&printf)(“…"); 變成 (&&printf)(“…"); 那麼出現以下 Error Message:

error: called object is not a function or function pointer

原因是, 第一次的 & operator 的運算後得到的結果是 int; 第二次進行 & operator 的時候, semantics 是 address of operand, 而 operand 就是 int. 所以就會出現 Error.


案例三和四是以上的變化,其原理是一樣的。


案例三

double mm = 111.17;

double* get_money (double m) {
    mm = m;
    return &mm; 
}

int main() {
    double* m1 = (&get_money)(3.7117718);
    printf("%d\n", (&printf)("%.3lf\n", *m1));

    double* m2 = (*&*&get_money)(7.3881171);
    printf("%d\n", (&printf)("%.5lf\n", *m2));

    return 0;
}

/*
 * gcc -w -std=c99 -o main main.c && ./main 
 * 3.712
 * 6
 * 7.38812
 * 8
 */


案例四

 struct bank {
    double money;
    float rate;
};

char* ary = {'A', 'B', 'C'};

char* get_char(void) {
    return ary + 1;
}

struct bank* get_bank(struct bank *b) {                                                 
    b->money = 11973.711;
    b->rate = 817.3;
    return b;
}

double get_money (double m) {
    return m;  
}

int main() {
    struct bank* btw = (struct bank *) malloc (sizeof (struct bank));
    btw->money = 117.37911;
    btw->rate = 3.718;

    struct bank* bmo = (***************************get_bank)(btw);

    printf("%d\n",  (****puts)("Hello"));
    printf("%lf\n", (******************get_money)(bmo->money));                         
    printf("%c\n",  (***************************************************************************************************************************************************************************************************************************************************************************get_char)());
    
    return (**************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************puts)("Hello World!!");                  
}

/*
gcc -g -w -std=c99 -o main main.c
./main 
Hello
6
11973.711000
B
Hello World!!
*/

-完-

廣告
C

利用 Bit-Trick 實作分支陳述式

根據 C 語言規格書中 6.8.4 所定義的 Context-free grammar

selection-statement:
          if ( expression ) statement
          if ( expression ) statement else statement
          switch ( expression ) statement

倘若我們選擇了第一條 production rule,那麼 gcc 一定會產生 Branch Instructions。基於效能上的需求,可以使用以下實作。

假設存在兩個有號整數: int32_t a, b; 和分支陳述式 if (b < 0) a++; , 那麼分支陳述式可以取代為 a -= b >> 31;

一開始我們定義 b 是一個有號整數,因此 b 右移 31 位後,其值必為 Sign Bit 。也就是說,如果 b > 0, 那麼 b >> 31 必為 0。如果 b < 0, 那麼 b >> 31 必為 -1。a -= -1; -> a = a - (-1) -> a = a + 1; -> a++;

可以用 GDB 來觀察

7 a = 3; b = -5; c = 7; d = 11;
8 a -= b >> 31;
9 c -= d >> 31;

p/x b
$1 = 0xfffffffb
p/x d
$2 = 0xb
p/x b >> 31
$3 = 0xffffffff
p/x d >> 31
$4 = 0x0

-完-

C

白板上的程式碼

幾天以前我被要求在白板之上,使用不多於三分鐘的時間,寫出 Big Endian to Little Endian 和 Little Endian to Big Endian 的 C 語言程式碼。

要實作出這兩個函式,除了要知道 MSB 和 LSB 的 Byte Order 不一樣之外,還要考慮 Data 是 Unsigned, 還是 Signed ? 是 32 位元,還是 64 位元?

我以 32 位元作為例子分享給大家。

轉換函式
int32_t ltob(int32_t num) {
    return (num >> 24 & 0x000000FF) ^
            (num >> 8 & 0x0000FF00) ^
            (num << 8 & 0x00FF0000) ^
            (num << 24 & 0xFF000000);  
}
測試程式
void main() {
    int32_t l = -0x12345678;
    int32_t b = ltob(l);
    while (1);
}
使用 GDB 進行驗証

$ gcc -g -o test test.c

(gdb) p/x l
$1 = 0xedcba988
(gdb) p/x b
$2 = 0x88a9cbed

不出所料,在 GDB 中能看到預期的結果。接著,在 GDB 中觀察以下的 Byte Order 變化。

(gdb) p/x l
$5 = 0xedcba988
(gdb) p/x l >> 24
$6 = 0xffffffed
(gdb) p/x l >> 24 & 0x000000FF
$7 = 0xed

從上述的觀察可以得出結論︰Operator Shift 決定了新的 Byte Order,Operator And 消除多餘的 Byte。同樣的道理可以轉換其他的 Byte ,最後用 XOR 組合它們即可。


[註]︰可以利用 Objdump 觀察機器的 Endian

void main() {
    int32_t l = 0x12345678; 
}

$ gcc -o test test.c
$ objdump -d test


  400589:   55                      push   %rbp
  40058a:   48 89 e5                mov    %rsp,%rbp
  40058d:   c7 45 fc 78 56 34 12   movl   $0x12345678,-0x4(%rbp)
  400594:   b8 00 00 00 00          mov    $0x0,%eax
  400599:   5d                      pop    %rbp
  40059a:   c3                      retq   
  40059b:   0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

– 完 –