1
考慮以下程式碼:
#include <stdio.h>
struct empty { };
int main() {
printf("%d\n", sizeof(void));
printf("%d\n", sizeof(struct empty));
}
在 gcc 編譯時指定 -std=c99
(參照 C99 規範) 輸出為以下兩行:
1
0
從以下選項中挑出最接近的描述。
注意:同樣的程式碼經過 C 和 C++ 編譯器產生的執行檔有截然不同的輸出
作答區
A = ?
(a)
這程式之所以能夠通過編譯,是因為 GNU extensions;(b)
C99/C11 規格書對此已有正面描述,執行結果符合預期;(c)
會有上述執行結果,顯示 gcc 編譯器內部的實作錯誤;延伸題目:
-pedantic
2
考慮以下程式碼:
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
char *p, *q;
uintptr_t pv, qv;
{
char a = 3;
p = &a;
pv = (uintptr_t) p;
}
{
char b = 4;
q = &b;
qv = (uintptr_t) q;
}
if (p != q) {
printf("%p is different from %p\n", (void *) p, (void *) q);
printf("%" PRIxPTR " is not the same as %" PRIxPTR "\n", pv, qv);
} else {
printf("Surprise!\n");
}
return 0;
}
這段程式碼在 macOS 和 GNU/Linux 有著不同的執行輸出:
0x7ffee37fbabf is different from 0x7ffee37fbabe
7ffee37fbabf is not the same as 7ffee37fbabe
Surprise!
從以下選項中挑出最接近的描述
作答區
B = ?
(a)
純粹只是 C 編譯器行為的落差,實務上不需特別留意;(b)
char a
和 char b
這兩個物件的生命週期僅分別在第 11 行和第 16 行有效,一旦超出 scope (作用範圍),C 語言編譯器就會主動釋放物件,而透過 &
(value-of) 運算子得到的地址自然就不再有效;(c)
char a
和 char b
這兩個物件的生命週期僅分別在第 11 行和第 16 行有效,一旦超出 scope (作用範圍) 仍操作物件的話,會導致 undefined behaviour;(d)
gcc 的實作不符合 C99/C11 規格描述;提示:
6.2.4:2
節提及物件的生命週期:The lifetime of an object is the portion of program execution during which storage is guaranteed to be reserved for it. An object exists, has a constant address, and retains its last-stored value throughout its lifetime. If an object is referred to outside of its lifetime, the behavior is undefined. The value of a pointer becomes indeterminate when the object it points to (or just past) reaches the end of its lifetime.
延伸問題:
PRIxPTR
為何?在哪裡定義?3
考慮以下 cmov (contional move) 指令的 C 程式實作:
void *cmoveq(int c, void *p1, void *p2) {
void *r = p1;
if (!c) r = p2;
return r;
}
在 Intel Pentium Pro (也稱為 P6 微架構) 後引入新指令 cmoveq
,於是上面的 C 程式等價於以下 x86_64 組合語言輸出:
cmoveq:
testl %edi, %edi
cmoveq %rdx, %rsi
movq %rsi, %rax
retq
關鍵不僅在於程式碼的密度 (code density),更在於少了分支,這對於密碼學的實作非常關鍵,依據 cryptocoding.net 的建議 Avoid branchings controlled by secret data,這可避免 timing attack。請改寫上述 C 程式,使其得以在常數時間內完成 cmoveq 等價功能。欲改寫的新程式碼如下:
#include <stdint.h>
void *cmoveq(int c, void *p1, void *p2) {
uintptr_t mask = -(!!c);
return (void *) (((uintptr_t) C1) | ((uintptr_t) C2));
}
請補完上述 C1
和 C2
程式碼。
作答區
C1 = ?
(a)
p1 | mask(b)
p1 ^ mask(c)
p1 & mask(d)
p1 | ~mask(e)
p1 ^ ~mask(f)
p1 & ~maskC2 = ?
(a)
p2 | mask(b)
p2 ^ mask(c)
p2 & mask(d)
p2 | ~mask(e)
p2 ^ ~mask(f)
p2 & ~mask參考資料:
現在的編譯器最佳化已能夠分析特定程式模式,並以硬體指令替代,如 clang/llvm 可將上述 cmov 程式轉為對應包含 cmov 指令的組合語言,實驗方式為:
$ clang -S -Ofast -fomit-frame-pointer -fno-asynchronous-unwind-tables cmov.c
檢查編譯器產生的 cmov.s
,確認裡頭包含 cmoveq
指令
延伸題目: