嚴格別名規則下的合法類型雙關:memcpy 與聯合體
利用 memcpy 或聯合體進行類型雙關,將浮點數位模式重新解讀為整數/RGB 像素,生成具有分形特徵的 PPM 圖像。回應「指標強制轉換有限制」的質疑。
核心程式碼 (C99 標準合規)
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#define DIM 1000
void write_pixel(FILE *fp, int x, int y) {
float t = y + x * 0.001f; // 計算浮點值
uint8_t rgb[3];
memcpy(rgb, &t, 3); // 合法類型雙關:複製位模式
fwrite(rgb, 1, 3, fp); // 寫入 RGB 三通道
}
int main() {
FILE *fp = fopen("image.ppm", "wb");
if (!fp) return 1;
// PPM 標頭:P6 = 二進位 RGB, 寬 高, 最大色值
fprintf(fp, "P6\n%d %d\n255\n", DIM, DIM);
for (int y = 0; y < DIM; y++) {
for (int x = 0; x < DIM; x++) {
write_pixel(fp, x, y);
}
}
fclose(fp);
return 0;
}
為什麼這是合法的?
| 方法 | 合法性 | 標準依據 |
*(int*)&float_var | ❌ 未定義行為 | C11 6.5/7 嚴格別名規則 |
union { float f; int i; } u; | ✅ 合法 (C99+) | C11 6.5.2.3/3 聯合體類型雙關 |
memcpy(&dst, &src, sizeof(dst)) | ✅ 合法 | C11 7.24.2.1 位模式複製 |
char* 別名 | ✅ 合法 | 字符類型可別名任何類型 |
生成圖像分析
t = y + x * 0.001f:x 變化 0.001,y 變化 1.0
- IEEE 754 float 位模式:
sign(1) | exponent(8) | mantissa(23)
- 取前 3 bytes (24 bits) 作為 RGB:捕獲指數位 + 尾數高位
- 結果呈現分形條紋圖樣,反映浮點數編碼的幂級結構
PPM 格式說明
P6 # 二進位 RGB 模式 (P3 為 ASCII)
1000 1000 # 寬 高
255 # 最大色值
[RGB 三元組連續資料...]
可用 ImageMagick convert image.ppm image.png 或線上檢視器開啟。
C++ 版本 (更型別安全)
// C++17 std::bit_cast (編譯期位模式轉換)
#include <bit>
#include <cstdint>
uint32_t float_to_rgb(float f) {
uint32_t bits = std::bit_cast(f);
return bits | 0xFF000000; // 加上 Alpha = 255
}
// 或使用聯合體 (C++20 起標準化)
union FloatInt { float f; uint32_t i; };
uint32_t bits = FloatInt{f}.i;
Why 數學圖像生成工具
作者開發的 Why 工具內建此演算法:
class CPixelFloatConvertInt : public IPixelEquation {
unsigned int CalculatePixel(unsigned int x, unsigned int y) {
float t = y + x * 0.001f;
unsigned int rst = *(unsigned int*)&t; // 內部實作用聯合體
rst |= 0xFF000000;
return rst;
}
};
教訓總結
- 指標強制轉換
*(T*)&v 違反嚴格別名規則,編譯器可假設不別名而誤優化
memcpy 與聯合體是標準合規的類型雙關方式
char* 可別名任何類型,但僅限字節級存取
- 現代 C/C++ 提供
std::bit_cast/__builtin_bit_cast 零開銷安全轉換
- 上一篇文章: C 開發者視角的 SICP 學習心得:抽象層次、編譯器實作與工程價值
- 下一篇文章: C語言實現四則運算計算器:遞歸下降解析器
Copyright ©
程式碼之家 All Rights Reserved