VS2019 Makefile 型工程调用 Clang

需求

VS2019 Makefile 型工程使用 Clang 编译,要同时支持 x86 和 x64 平台。

问题

找不到 Clang 参数可以指定目标平台,如果您知道请不吝赐教。(github 账号:UMU618)

解决

同时安装这两个平台的 Clang。x86 的由 VS2019 自带,在 VS 中被表示为 "$(ClangAnalysisToolsPath)\clang.exe",宏展开后为:

1
2
3
4
5
$ &"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\Llvm\bin\clang.exe" -v
clang version 10.0.0
Target: i686-pc-windows-msvc
Thread model: posix
InstalledDir: C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\Llvm\bin

x64 是另外安装的,安装时选择把目录注册到 PATH:

1
2
3
4
5
$ clang -v
clang version 10.0.0
Target: x86_64-pc-windows-msvc
Thread model: posix
InstalledDir: C:\Program Files\LLVM\bin

结论:只需在 Win32 的平台设置使用 "$(ClangAnalysisToolsPath)\clang.exe",而 x64 则直接使用 clang(如果安装时忘记注册到 PATH,需要手动添加)。

SDL/Windows 代码分析【1】thread

1. SDL_semaphore

代码:src\thread\windows\SDL_syssem.c

别名:SDL_sem

1
typedef struct SDL_semaphore SDL_sem;

基于 WinAPI 匿名 Semaphore 封装。MaximumCount 硬编码为 32 * 1024。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
struct SDL_semaphore
{
HANDLE id;
LONG count;
};


/* Create a semaphore */
SDL_sem *
SDL_CreateSemaphore(Uint32 initial_value)
{
SDL_sem *sem;

/* Allocate sem memory */
sem = (SDL_sem *) SDL_malloc(sizeof(*sem));
if (sem) {
/* Create the semaphore, with max value 32K */
#if __WINRT__
sem->id = CreateSemaphoreEx(NULL, initial_value, 32 * 1024, NULL, 0, SEMAPHORE_ALL_ACCESS);
#else
sem->id = CreateSemaphore(NULL, initial_value, 32 * 1024, NULL);
#endif
sem->count = initial_value;
if (!sem->id) {
SDL_SetError("Couldn't create semaphore");
SDL_free(sem);
sem = NULL;
}
} else {
SDL_OutOfMemory();
}
return (sem);
}

2. SDL_mutex

代码:src\thread\windows\SDL_sysmutex.c

基于 WinAPI CriticalSection 封装。SpinCount 硬编码为 2000,即在多处理器系统上,如果无法立刻进入临界区,则会自旋最多 2000 次,然后等待 CriticalSection 内部关联的信号量。只要在自旋过程中其它线程退出临界区,则无需进入等待状态。这么做是提高效率,自旋时当前线程还占着 CPU,如果进入等待状态,就是交出 CPU 时间片了,而 CPU 调度是个消耗型操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
struct SDL_mutex
{
CRITICAL_SECTION cs;
};

/* Create a mutex */
SDL_mutex *
SDL_CreateMutex(void)
{
SDL_mutex *mutex;

/* Allocate mutex memory */
mutex = (SDL_mutex *) SDL_malloc(sizeof(*mutex));
if (mutex) {
/* Initialize */
/* On SMP systems, a non-zero spin count generally helps performance */
#if __WINRT__
InitializeCriticalSectionEx(&mutex->cs, 2000, 0);
#else
InitializeCriticalSectionAndSpinCount(&mutex->cs, 2000);
#endif
} else {
SDL_OutOfMemory();
}
return (mutex);
}

3. SDL_cond

代码:src\thread\windows\SDL_syscond.c

基于 SDL_mutex 和 SDL_sem 封装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
struct SDL_cond
{
SDL_mutex *lock;
int waiting;
int signals;
SDL_sem *wait_sem;
SDL_sem *wait_done;
};

/* Create a condition variable */
SDL_cond *
SDL_CreateCond(void)
{
SDL_cond *cond;

cond = (SDL_cond *) SDL_malloc(sizeof(SDL_cond));
if (cond) {
cond->lock = SDL_CreateMutex();
cond->wait_sem = SDL_CreateSemaphore(0);
cond->wait_done = SDL_CreateSemaphore(0);
cond->waiting = cond->signals = 0;
if (!cond->lock || !cond->wait_sem || !cond->wait_done) {
SDL_DestroyCond(cond);
cond = NULL;
}
} else {
SDL_OutOfMemory();
}
return (cond);
}

SDL_CondWaitTimeout 实现较长,本文忽略。重点是:为了避免死锁,它进入等待前,会先解锁第二个参数 mutex。如果不这么做,其它线程也要 Lock 这个 mutex 就会发生死锁。

以下代码是典型用法,线程 A 进入临界区后,SDL_CondWait(内部调用 SDL_CondWaitTimeout)会调用 SDL_UnlockMutex(lock); 使得线程 B 可以进入临界区调用 SDL_CondSignal(cond);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Typical use

// Thread A:
SDL_LockMutex(lock);
while (!condition) {
SDL_CondWait(cond, lock);
}
SDL_UnlockMutex(lock);

// Thread B:
SDL_LockMutex(lock);
condition = true;
SDL_CondSignal(cond);
SDL_UnlockMutex(lock);

4. SDL_Atomic

代码:src\atomic\SDL_atomic.c

基于 _Interlocked API 封装。此类原子操作一般底层实现都是相应平台的汇编指令(比如 x86 平台是 lock cmpxchg 之类),但在不同平台下会有不同的封装集,所以 SDL_atomic.c 里有很多平台相关的宏判断。

5. SDL_MemoryBarrier

代码:src\atomic\SDL_atomic.h

内存屏障。参考文章:Acquire and Release Semantics

在 Windows x86 环境下等价于 _ReadWriteBarrier:

1
2
3
4
5
6
7
void _ReadWriteBarrier(void);
#pragma intrinsic(_ReadWriteBarrier)
#define SDL_CompilerBarrier() _ReadWriteBarrier()

/* This is correct for the x86 and x64 CPUs, and we'll expand this over time. */
#define SDL_MemoryBarrierRelease() SDL_CompilerBarrier()
#define SDL_MemoryBarrierAcquire() SDL_CompilerBarrier()

  • Acquire semantics is a property that can only apply to operations that read from shared memory, whether they are read-modify-write operations or plain loads. The operation is then considered a read-acquire. Acquire semantics prevent memory reordering of the read-acquire with any read or write operation that follows it in program order.

  • Release semantics is a property that can only apply to operations that write to shared memory, whether they are read-modify-write operations or plain stores. The operation is then considered a write-release. Release semantics prevent memory reordering of the write-release with any read or write operation that precedes it in program order.

生硬的翻译如下:

  • 获取语义是一个属性,它只能应用于从共享内存读取的操作,无论是“读取-修改-写入”操作还是普通加载。该操作将被视为“读取获取”。获取语义可防止“读取获取”和它之后的读取或写入操作发生内存重新排序。

  • 释放语义是一个属性,它只能应用于写入共享内存的操作,无论是“读取-修改-写入”操作还是普通存储。该操作将被视为“写入释放”。释放语义可防止“写入释放”和它之前,它之前的任何读取或写入操作发生内存重新排序。

以 x86 内存模型为例说明:

  • Loads are not reordered with other loads.
  • Stores are not reordered with other stores.
  • Stores are not reordered with older loads.
  • Loads may be reordered with older stores to different locations.

因为 store-load 可以被重排,所以 x86 不是顺序一致。但是其他三种读写顺序不能被重排,所以 x86 是 acquire/release 语义。

aquire 语义:load 之后的读写操作无法被重排至 load 之前。即 load-load, load-store 不能被重排。

release 语义:store 之前的读写操作无法被重排至 store 之后。即 load-store, store-store 不能被重排。

6. SDL_TLSData

意义:TLS,即 Thread Local Storage(线程局部存储)。

代码:src\thread\SDL_thread_c.h 和 src\thread\windows\SDL_systls.c

基于 SDL_Atomic、SDL_MemoryBarrier 和 WinAPI Tls API 封装。

以下结构体包含一个析构函数的指针,非空时,SDL_TLSCleanup() 会调用它。

1
2
3
4
5
6
7
8
/* This is the system-independent thread local storage structure */
typedef struct {
unsigned int limit;
struct {
void *data;
void (SDLCALL *destructor)(void*);
} array[1];
} SDL_TLSData;

7. SDL_Thread

代码:src\thread\SDL_thread.c 和 src\thread\windows\SDL_systhread.c

创建线程的 API 是 SDL_CreateThread 和 SDL_CreateThreadWithStackSize,导出函数 SDL_CreateThread 的定义如下,记为【X】:

1
SDL_DYNAPI_PROC(SDL_Thread*,SDL_CreateThread,(SDL_ThreadFunction a, const char *b, void *c, pfnSDL_CurrentBeginThread d, pfnSDL_CurrentEndThread e),(a,b,c,d,e),return)

下面会有递归展开宏的过程。首先,用 SDL_DYNAPI_PROC 的定义:

1
2
3
4
5
#define SDL_DYNAPI_PROC(rc,fn,params,args,ret) \
static rc SDLCALL fn##_DEFAULT params { \
SDL_InitDynamicAPI(); \
ret jump_table.fn args; \
}

展开【X】得到:

1
2
3
4
static SDL_Thread* __cdecl SDL_CreateThread(SDL_ThreadFunction a, const char *b, void *c, pfnSDL_CurrentBeginThread d, pfnSDL_CurrentEndThread e) {
SDL_InitDynamicAPI();
return jump_table.SDL_CreateThread(a,b,c,d,e);
}

其中 jump_table.SDL_CreateThread 是【X】被 SDL_dynapi_procs.h 的:

1
2
3
4
5
6
7
8
9
10
11
12
13
/* The jump table! */
typedef struct {
#define SDL_DYNAPI_PROC(rc,fn,params,args,ret) SDL_DYNAPIFN_##fn fn;
#include "SDL_dynapi_procs.h"
#undef SDL_DYNAPI_PROC
} SDL_DYNAPI_jump_table;

/* The actual jump table. */
static SDL_DYNAPI_jump_table jump_table = {
#define SDL_DYNAPI_PROC(rc,fn,params,args,ret) fn##_DEFAULT,
#include "SDL_dynapi_procs.h"
#undef SDL_DYNAPI_PROC
};

展开得到,为:

1
2
3
4
5
6
7
8
9
10
11
typedef struct {
/* ... */
SDL_DYNAPIFN_SDL_CreateThread SDL_CreateThread;
/* ... */
} SDL_DYNAPI_jump_table;

static SDL_DYNAPI_jump_table jump_table = {
/* ... */
SDL_CreateThread_DEFAULT,
/* ... */
};

【X】又被 SDL_dynapi.c 的 initialize_jumptable 函数的:

1
2
3
4
/* Init our jump table first. */
#define SDL_DYNAPI_PROC(rc,fn,params,args,ret) jump_table.fn = fn##_REAL;
#include "SDL_dynapi_procs.h"
#undef SDL_DYNAPI_PROC

展开为:

1
2
3
/* ... */
jump_table.SDL_CreateThread = SDL_CreateThread_REAL;
/* ... */

所以,调用 SDL_CreateThread 最终调用的就是 SDL_CreateThread_REAL,又由于 src\dynapi\SDL_dynapi_overrides.h 中的:

1
#define SDL_CreateThread SDL_CreateThread_REAL

所以调用的是 src\thread\SDL_thread.c 中的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#ifdef SDL_PASSED_BEGINTHREAD_ENDTHREAD
DECLSPEC SDL_Thread *SDLCALL
SDL_CreateThread(int (SDLCALL * fn) (void *),
const char *name, void *data,
pfnSDL_CurrentBeginThread pfnBeginThread,
pfnSDL_CurrentEndThread pfnEndThread)
#else
DECLSPEC SDL_Thread *SDLCALL
SDL_CreateThread(int (SDLCALL * fn) (void *),
const char *name, void *data)
#endif
{
/* !!! FIXME: in 2.1, just make stackhint part of the usual API. */
const char *stackhint = SDL_GetHint(SDL_HINT_THREAD_STACK_SIZE);
size_t stacksize = 0;

/* If the SDL_HINT_THREAD_STACK_SIZE exists, use it */
if (stackhint != NULL) {
char *endp = NULL;
const Sint64 hintval = SDL_strtoll(stackhint, &endp, 10);
if ((*stackhint != '\0') && (*endp == '\0')) { /* a valid number? */
if (hintval > 0) { /* reject bogus values. */
stacksize = (size_t) hintval;
}
}
}

#ifdef SDL_PASSED_BEGINTHREAD_ENDTHREAD
return SDL_CreateThreadWithStackSize(fn, name, stacksize, data, pfnBeginThread, pfnEndThread);
#else
return SDL_CreateThreadWithStackSize(fn, name, stacksize, data);
#endif
}

可见 SDL_CreateThread 调用了 SDL_CreateThreadWithStackSize,而 SDL_CreateThreadWithStackSize 又调用 src\thread\windows\SDL_systhread.c 中的 SDL_SYS_CreateThread,因为 Windows 平台有 _beginthreadex_endthreadex,所以最后是调用 _beginthreadex

1
2
3
4
5
6
7
8
9
10
11
12
13
/* thread->stacksize == 0 means "system default", same as win32 expects */
if (pfnBeginThread) {
unsigned threadid = 0;
thread->handle = (SYS_ThreadHandle)
((size_t) pfnBeginThread(NULL, (unsigned int) thread->stacksize,
RunThreadViaBeginThreadEx,
pThreadParms, flags, &threadid));
} else {
DWORD threadid = 0;
thread->handle = CreateThread(NULL, thread->stacksize,
RunThreadViaCreateThread,
pThreadParms, flags, &threadid);
}

其中 RunThreadViaBeginThreadEx 实际上是调用 RunThread:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static DWORD
RunThread(void *data)
{
pThreadStartParms pThreadParms = (pThreadStartParms) data;
pfnSDL_CurrentEndThread pfnEndThread = pThreadParms->pfnCurrentEndThread;
void *args = pThreadParms->args;
SDL_free(pThreadParms);
SDL_RunThread(args);
if (pfnEndThread != NULL)
pfnEndThread(0);
return (0);
}

static DWORD WINAPI
RunThreadViaCreateThread(LPVOID data)
{
return RunThread(data);
}

static unsigned __stdcall
RunThreadViaBeginThreadEx(void *data)
{
return (unsigned) RunThread(data);
}

从代码可见 RunThread 调用 SDL_RunThread,而 SDL_RunThread 内部由 SDL_TLSCleanup() 来调用析构函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
void
SDL_RunThread(void *data)
{
thread_args *args = (thread_args *) data;
int (SDLCALL * userfunc) (void *) = args->func;
void *userdata = args->data;
SDL_Thread *thread = args->info;
int *statusloc = &thread->status;

/* Perform any system-dependent setup - this function may not fail */
SDL_SYS_SetupThread(thread->name);

/* Get the thread id */
thread->threadid = SDL_ThreadID();

/* Wake up the parent thread */
SDL_SemPost(args->wait);

/* Run the function */
*statusloc = userfunc(userdata);

/* Clean up thread-local storage */
SDL_TLSCleanup();

/* Mark us as ready to be joined (or detached) */
if (!SDL_AtomicCAS(&thread->state, SDL_THREAD_STATE_ALIVE, SDL_THREAD_STATE_ZOMBIE)) {
/* Clean up if something already detached us. */
if (SDL_AtomicCAS(&thread->state, SDL_THREAD_STATE_DETACHED, SDL_THREAD_STATE_CLEANED)) {
if (thread->name) {
SDL_free(thread->name);
}
SDL_free(thread);
}
}
}

八哥之神前传【13】

618 年,识界

圣仙山:两位娘子,待会儿去吾姐梦里和她告别。

李心觎:为啥不在视界亲自和她告别呢?

圣仙山:她远嫁黑龙江,吾在视界又不能飞,要跑很远纳!再说吾在视界还没娶妻,多没面子呀。

李心觎:嘻嘻嘻。

李冰月:哈哈哈!姐姐,咱们是去给相公撑场面的!

李神源梦中

圣仙山:神姐!

李神源:嗯?是仙弟呀。这两位是?

圣仙山:是小弟的妻子。

李冰月、李心觎:姐姐好!

李神源:好好好。两位弟妹灵秀如仙,仙弟什么时候得此艳福,真是好大的惊喜呀!

李冰月:好说好说。相公才是灵秀如仙,我们姐妹俩还不如他呢!

李心觎:是呀,神姐,夫君才是真仙。

李神源:哎呀,我这仙弟以前老和我说,他要一心求道,不想娶妻生子,还说未来的人越来越多都不结婚的。

圣仙山:姐!现在情况有变,当今皇帝受老钧托梦,要老爹组织一群谷阳人去南荒开疆传道,同行人中有技师文人,也有流民和被大赦的罪犯,还要途径苦境各地,和三教九流打交道,我是最佳带队人选。

李神源:此去危险重重,仙弟为何不让别人去呢?

圣仙山:除了传道,吾还有别的任务,要去寻找吾心中的道州德国,一处远离天灾人祸的修道圣地。

李冰月:神姐放心,相公已经修成八识神通,这事可以安全办成。

李神源:既然如此,为姐只能祝你一切顺利。我养了一只守护灵狐,让它陪你上路吧,风餐露宿有危险时,它会警示你。

圣仙山:谢谢神姐。

识界

李心觎:神姐梦里的灵狐好可爱耶。

李冰月:是呀,咱们在识界里找一只呗!

李心觎:走。

李冰月:相公,这只叫“纯狐连玉”,你在视界如果遇到它,它会认得你的。

李心觎:连玉,暂时就陪我们吧。

少曾读仙史,知有苏耽君。流望来南国,依然会昔闻。
泊舟问耆老,遥指孤山云。孤山郴郡北,不与众山群。
重崖下萦映,嶛峣上纠纷。碧峰泉附落,红壁树傍分。
选地今方尔,升天因可云。不才予窜迹,羽化子遗芬。
将览成麟凤,旋惊御鬼文。此中迷出处,含思独氛氲。

2003 年

陈因提:圣仙山带着一群中原人来到现在你家这个地方?

圣小开:对!仙山公在路上娶了胡小玉,才有现在的我。

陈因提:胡小玉很特别吗?娶别人不一样有你?

圣小开:万一不娶呢?那我不是生不出来!

陈因提:哈哈。你真是瞎操心。我比较担心他死在路上,而你担心他不娶!

圣小开:有神通在,明显死不了呀。料事如神,有啥危险或者人际关系的问题,都能预防。

陈因提:哦,后世人神化的吧!成王败寇。

圣小开:仙山公能找到这个好地方本身就很神了,台风地震海啸全被周围其他地方挡住,几乎没天灾,还远离政治和战乱,在当时可谓世外桃源。

陈因提:这是现实,暂且信你。那么,你有遗传到这八识神通吗?

圣小开:略懂略懂!八识神通分为被动接受和主动测算,我有时候也能接受到来自未来的预警,主动测算我是不会。而且主动测算会耗费心力,即所谓招天谴。仙山公测算多次,导致 35 岁就仙逝了。胡小玉就活了 42 岁,他们女儿活到 64 岁。

陈因提:算出啥了?老天爷这么急着收他?

圣小开:回归十界。

陈因提:啥意思?

圣小开:不是老天收他,是他终于算出怎么回归十界,便抛下肉身……

陈因提:等等,说人话!

圣小开:算出怎么去死!

陈因提:切!还以为啥高深的法门……睡了睡了。

挨踢人必备 Embody

故事从 2012 年开始

这一年在 59 号大网吧办公,总感觉桌子太矮椅子太烂……心神不宁,频繁想起一个故事:

某同事很低调,从来不炫耀自己的成就,突然有一天,他说:“我太突出了!”

一问,原来是腰间盘突出……

这一年,还是哥的稣决定攒钱买一把好的椅子。

存了几个月,终于买了一把保友金豪 E-AB-HAM。那时好多同事来围观,有 HR 来说了句:“比总经理用的还好!”,也有不少小伙伴说长得像医疗用的复健椅。

**但是这把椅子在稣心里并没有地位。**当时已经有智能手机的稣,从来没有拍过它,以致如今想查具体啥时候买的,都不那么确定。只记得是花 3800 元买的,最晚不会超过 2013-01-19 这个时间!

大约的购买时间

当时还很撒逼地把能选配的全给配上……实际使用证实左手边的书架几乎用不上,以致后来把它给拆下来了!脚垫也不常用,很少在公司午睡。

说起效果,那确实比公司的乐射椅子好很多,但也不是特别舒服。网布很透气,但在公司有空调的前提下,透气并不是那么必须。反而缺点就是它并没有根据屁股的造型来支托,简单地说有点勒腿,坐久了并不舒服。所以,后来又研究起 Embody。

开始了解 Embody

后来有土豪要买 Embody,稣跟着去体验了一下。2020 年,土豪又买了第二张……

直到 2020 年才有钱买

2020-06-10 下单乐歌升降桌 E3,180*80cm。靠自动提醒功能养成坐久了站会儿的习惯,结果反而让稣发现八哥就在金豪这把椅子上!网布还是个问题,坐 8 年已经变软一些,但依然有一种压迫大腿两侧的酸麻感。

吓得稣赶紧下单,买了介个:

Embody 廉价替代品

这下终于明白形状和支撑的重要性!果断存钱买 Embody!

存了一个月多的钱,再加上脑波给的一万块人民币,终于入手 Embody!

体会

  • Embody 可以调得比金豪更矮,适合的身高范围就更大,尤其对女性友好。腿、屁股和腰都舒服。

  • 一张 Embody 价格可以买金豪 3 张以上,但是肯定是没有 3 倍的舒服,高端的东西性价比自然比大众款低的多,使用价值只能在细节上轻微地增加。好在大腿的问题真的解决了。

  • 挨踢族一天坐在椅子上的时间是远超健康上限的。稣并不赞成通过买任何东西来装逼,除非这东西是真实用,所以贵。Embody 对很多人来说,可能就是装逼,但对挨踢人绝对不是。

并不是主角才有光环,而是有光环的才能当主角,这才是生活。——泥巴娃

  • 挨踢人就应该对自己好一点。

最后,一句话总结:要不是穷,真想再买一张!

八哥之神前传【12】

识界

李仙山,字太墟,号道识,是最后一名通过修行进入识界的人类,最为人津津乐道的是他娶了两位貌美如花的妻子:李冰月和李心觎。三人同在深山修炼,羡煞旁人。

李仙山:两位娘子,山顶那朵魔莲原来是老钧的智慧所化,吾已得其认可,纳入脑识。

李冰月:那相公还是相公吗?

李心觎:是呀,夫君不会鲸神魂裂吗?

李仙山:哈哈,不仅没有副作用,还助吾练就八识神通。

李心觎:听起来好厉害哦!有何用处呢?

李冰月:哎呀!相公的眼睛怎么流血了!

李仙山:咦?难道不能预测自己的未来?

李心觎:怎么回事也?赶紧擦擦。

李仙山:八识神通能够预知未来,吾刚刚测算视界中自己的未来,发现未来的视界大部分地方都实行一夫一妻制,吾之转世因此无法娶三个妻子。

李冰月:测算自己会破坏因果循环,必遭天谴,相公可别乱用。

李仙山:只要不测自己就没事。吾试过预测识界未来,发现识界将关闭千余年,后有邪灵借助科技的力量强行进入识界,后使视界的普通人不必修行亦可进入识界。可谓千年浩劫!

李冰月:此事非同小可,需召集众仙请示老钧。

李仙山:不必如此兴师动众,为夫一人已可召唤老钧。

李心觎:老钧居于十维之外,三千年来只见众人祈祷之法,却无人见过老钧现身。夫君真能召唤出来?

李仙山:老钧的意志无处不在,只要用八识神通与他纠缠就可以接受他的意识传播。来!

突然云雾缭绕的道州蓬山化入星辰,创世天尊老钧自虚无中发出一道引力波。

李仙山:小孙,拜见老祖,请老钧指示应对浩劫之方。

老钧:道之不传,识界将倾。回到视界,去南方寻找道州德国。

李仙山:领道旨。

老钧:天机不可泄露!切记不可再测算自己的未来。从今而后,改姓圣吧!

李仙山:这个姓小孙恐怕承受不起呀!再说不久前才从老改为李的,又改?

老钧:视界中的李伯阳会助你,让当朝皇帝赐姓。

李仙山:遵旨!

老钧消失前,喃喃道:那厮岂非汝乎?小孙杂……

回到道州蓬山。

圣仙山:两位娘子,为传道法,吾将离开中原,奔波于南荒之地,恐少有时间再入识界。

李冰月:相公是去干大事的,不用担心我们,我们会照顾好自己的。

李心觎:对呀,夫君偶尔来看我们就好。小别胜新婚,也许更好呢!

圣仙山:哈哈哈!

李冰月:姐姐,我们写几句诗送给相公吧!

李心觎:好呀。妹妹是大老婆,你先来。

李冰月:晓镜但愁云鬓改,夜吟应觉月光寒。蓬山此去无多路,青鸟殷勤为探看。

李心觎:昨夜星辰昨夜风,画楼西畔桂堂东。身无彩凤双飞翼,心有灵犀一点通。

圣仙山:好诗!好诗!都是你们未来的荥阳老乡写的!

李冰月、李心觎:哈哈哈。

圣仙山:再过两百多年义山就出生了!哈哈哈……

2003 年

陈因提:圣仙山就是你老祖宗?

圣小开:当然,所以我遗传了他的特异功能。

陈因提:哦……我有点困!你最好拿出能提神的证据。

圣小开:我偶尔也能进入识界!一般人我不告诉他。

陈因提:好啦好啦,那你说说看,你都看到啥?

圣小开:众生皆苦!

陈因提:阿弥陀佛。我裤子都脱了,你给我念经?

圣小开:耶!你怎么知道我还写过一本经书。

陈因提:拿出来,我帮你烧给你老祖宗。

圣小开:切!这本《八哥之神创世手稿》十分珍贵,里面记载着进入识界的修炼法门。

陈因提:一本九毛九?我买,明天给你一百块,免找。

圣小开:给你也好,要是我突然遭天谴,起码这绝学不会就此失传。拿去,好好保管。

陈因提:哟,字体工整,比写情书还认真!那我就收了,放心吧,我当宝一样收藏。

牛媒体装逼录

听说鲁豫要来采访稣

3Q

鹿邑:听说稣已经从区块链开发转型为牛媒体开发,能说说牛媒体吗?

稣:牛媒体就是三个 Q。

鹿邑:您是说三个季度就能掌握牛媒体?

稣:不是……是说牛媒体不过就是三个队列。

鹿邑:哦?我不会问您是哪三个!

稣:第一个是网络包队列,第二个是音视频包队列,第三个是音视频帧队列。

转码

鹿邑:那经常听到的“转码”到底是啥?

稣:标准多到蛋疼罢了。你的化妆桌,东西怎么摆你说了算,别人摆法不同,但东西是一样的。你搬家时,把它们装起来,用啥来装,怎么装,扔掉哪些不重要的,也是你说了算,别人装法不同,但关键的几样东西还是一样的。

鹿邑:据说转码很烧钱,你们用的机器都很贵?

稣:没有没有,小米游戏本就够装逼。

光栅化

鹿邑:第一次听到“光栅化”这个词时,表示一脸懵逼,给解释一下呗?

稣:稣拿稣法做类比,您就能理解了!Look!

地头力,图片来自百度,如有侵权请联系本人删除。

鹿邑:这就是光栅化?还是没明白呀!

稣:别急!您看这些笔划……稣大笔一挥,横竖撇捺,点折弯钩,一笔走红尘,两笔惊鬼神。稣的内心只有笔法走势。

鹿邑:牛逼死了!赶紧发朋友圈!

稣:没错!关键就是发票圈!为了用现代化的方式传播稣法,咱们可以扫描打印、刊登到报纸、拍照发票圈,这些手段有个共同点——最后出来都是一些像素点。

鹿邑:还真是这样哦!我以前拿放大镜看过屏幕,都是红绿蓝的像素点。

稣:您的理解能力不错。这个把“笔划”变成一系列点的过程就是“光栅化”。举个例子,您在画板上拉一条直线,计算机只需要知道头尾两个点和颜色就能定义这条直线,而光栅化之后就是 N 个这种颜色的像素点。

游戏

鹿邑:疫情以来游戏股涨势普遍大好,想了解一下我现在学游戏开发来得及吗?

稣:哈呵,游戏其实就是利用游戏引擎,根据玩家们的输入动作渲染声音和画面,很容易理解,不过实际开发起来很多坑,您还是买在港股上市的游戏公司的股票就好。

鹿邑:游戏引擎是什么,渲染又该怎么理解?

稣:游戏引擎是管理图像、声音、人机设备等一套套接口的集合。最重要的是 3D 引擎,简单的说,它是您告诉它“画个圆”,它就帮您“画出一些点,这些点组成一个圆”的接口。单说画面的渲染,可以理解为更多步骤更广义的光栅化,产生显存里的像素点数组,最后扔给显示器。

鹿邑:您又要类比吗?

稣:Look!稣写作的地方都在这些纸上,写得好的,会签名、盖章,拍照拿去发票圈。过程很简单,但包含了几个重要概念。纸张相当于显存;那张拿去拍照发票圈的纸是后台缓冲区(back buffer)的渲染目标(render target);盖章环节相当于是输出合并器阶段(output-merger stage);拍照可以理解为整体光栅化;当然拍照时,摄像头的位置、角度,以及打灯也是有讲究的;最后发票圈就当作输出到显示器吧!

鹿邑:好像不难理解了,您总是把啥都说得很简单,真这么简单吗?

稣:入门难而已!精通,则更难……不过一开始如果把一些专有名词、缩写整明白,可以更快入门。比如 Direct3D 的 API 就存在不少缩写,什么 OM、RS 啊,只看代码不看文档的话很容易懵逼。

鹿邑:数学要很好吧?

稣:数学思维要很好,具体的知识可以现学。几乎每步都有数学的应用,但套路比较有限,可以一通百通。举个最简单的例子:AlphaBlend 算法,常用于把人物、道具合成到背景图里,它其实和盖章是类似的,把章盖在字上,既可以看到字,也可以看到章。其计算公式就是用 alpha 值对背景点、前景点的三原色值做个加减乘运算,理解一下就行,都不用背。由于 YUV 有很多种具体规范,记不住,干脆也甭记,只要明白有些处理 YUV 比 RGB 方便,有些则 RGB 更方便就行。RGB 和 YUV 之间的转化也都有公式,一样只要理解不用记。

鹿邑:您觉得难点在哪里呢?

稣:规范太多!就拿最基础的像素点来说,就存在很多种格式,大的分类就有 RGB 和 YUV 系列,按每个分量(R、G、B、Y、U、V)的排序就可以分出好多种,还有按每个分量几位表示来区分,然后这些位是整数、浮点数或者浮点数规范化的整数,整数是有符号还是无符号都可以造成不同。

鹿邑:停!我已经晕了!

稣:就好像人以群分吧!但可以按照性别、年龄、肤色、籍贯、身高、体重、专业、爱好等各种维度细分,现实世界就有诸多标准,计算机里一样一样的。

鹿邑:明白!人艰不拆,没有容易二字。

稣:该睡了,做梦容易……吓醒。

诗盗·孙子编码规范

《#诗盗#·孙子编码规范》:善编码者,架不重构,洞不三补。码至难者缺人,缺人则贫于招人。财竭则急于裁员。

注解

模仿自春秋时吴国将军孙武的《孙子兵法》:

善用兵者,役不再籍,粮不三载,取用于国,因粮于敌,故军食可足也。
国之贫于师者远输,远输则百姓贫;近师者贵卖,贵卖则百姓财竭,财竭则急于丘役。
力屈、财殚,中原、内虚于家,百姓之费,十去其七;公家之费,破军罢马,甲胄矢弩,戟楯蔽橹,丘牛大车,十去其六。
故智将务食于敌,食敌一钟,当吾二十钟;忌杆一石,当吾二十石。

八哥之神【番外篇9】

听说鲁豫要来采访稣 请戴口罩采访

1. 疫情已经好转很多,终于又可以采访稣!最近在忙什么呢?

忙跳槽。最近两次采访之间,稣一共任职三个公司。

稣现在从事什么行业?

“AIoT 是我的爱,绵绵的金山脚下花正开”难道这歌,稣会唱给你听?

唱的还行,您随意。

“什么样的加班是最呀最摇摆,什么样的养生才是最开怀……”

2. 近期剧情似乎给配角更多笔墨?

是的。这是为了让主角晚点死,是拖剧的惯用套路。

樱国是映射日本吗?

如果您认真阅读,就会发现一个细节:《八哥之神前传【9】》里都是说“樱国”,而《八哥之神前传【10】》中施付说的是“日本”。

是哦,这是不小心写错吗?

不是。这是有区别的。《八哥之神前传【9】》里的故事发生在识界,而《八哥之神前传【10】》的故事则发现在咱们这个世界。

3. 上次您告诉我会出现“李心觎”,但《八哥之神前传【10】》出现的却是“李星觎”,这次是写错了吧?

当然不是。这是剧情需要,后面会解释清楚的,先不剧透。

4. 出现“狐狸精”这种神话色彩的人物,是不是违反您之前说的“无神论”?

这个问题稍微复杂点。这故事发生于 1994 年秋天,圣小开才 12 周岁不到。

他只是个孩子呀!!!

被一系列恐怖的景象吓坏,产生幻觉您可以理解吧!

而且后来开讲给陈因提听时,她并未相信。说明这也可能只是开一脸正经讲的鬼故事而已,作为最了解开的陈因提都如此认为,读者有啥理由拿它当神话看?

原来如此,这可以理解为主角的性格塑造吧!

没错!您想,大部分男人要三四十岁才会遇到狐狸精,而开十岁出头就遇到,而且免疫,这难道不够装逼?

5. 稣,您又赢了!另外,“纯狐连玉”和之前出现的“胡小玉”是同一只吗?

显然不是呀!您看,演员都不是同一个!胡小玉是白色的,长相冷艳,气质是性感魅惑型。纯狐连玉,光看姓,就知道是上古神话风,走的是庄严又有点幽默的大姐路线。

胡小玉只存在于圣小开梦中,是圣小开见过的某类美女的凝神具体。名字很像,主要原因是胡小玉的最初形象是按照纯狐连玉梦见的,后来再也没见过纯狐连玉,形象不断模糊,又不断得到其她美女形象加强就进化出胡小玉。

纯狐连玉是真的狐狸精,而胡小玉更像人。

6. 稣的梦究竟是什么奇妙的世界?能用最贴切的语言描述分享吗?

  • 脑的触感比人体表面最敏感的皮肤还敏感。

  • 爱情里只有鲸神链那部分才是最深刻的。

7. 陈博士的干儿子长生和古思是怎么回事?

孟长生是早期基因编辑婴儿里比较成功的一个,性别染色体来自圣小开,其它基因都来自陈博士,以此为模板优化而成。所以被陈博士收为干儿子,并安排在贾总公司担任一个小领导。

古思是完全基因编辑人造人,只有卵巢是根据陈博士的基因设计,专门用于代孕。这在未来很流行哦!现在大家可能还难以接受。

没关系,等大家有钱再说。

8. 还是有很多读者表示看不懂,您能再提示一下吗?

一部剧能不能看,首先选角很重要,然后靠演员、导演、剧组的努力配合,后期制作、宣传也很重要。

稣只是一个编剧啊!看不懂绝壁不是编剧的问题,您下次采访一下各位演员吧!

八哥之神前传【11】

2049 年

陈博士:长生最近怎么样?

贾力劣:工作尽心尽职无可挑剔,就是好多女员工都打他的主意。

陈博士:难道身份泄露了?

贾力劣:不会!这事严格保密,连他自己都不知道。

陈博士:那就好。

贾力劣:不过您的干儿子年纪也不小,女朋友经常换,坚持不结婚,难免被人说三道四。

陈博士:嗨,这就是基因的力量,思想都差不多。

贾力劣:嗯,他的基因几乎和您一样。

陈博士:他比我还过分,我只是不想生孩子,找个丁克结婚还是可以的。

贾力劣:女朋友换得勤,恐被称渣男呢。

陈博士:他只是个孩子呀!

贾力劣:哦!em……呃!那个,按您基因设计出来的代孕女孩已经送给开哥。

陈博士:很好。这才是正事!管我干儿子的私生活,活腻了?

贾力劣:是是是。她的卵细胞 100% 和您一样。您放一百个心。

陈博士:贾总办事就是靠谱,尤其是保密工作。

贾力劣:懂!相关资料已经销毁。

1994 年

黄金灯已经是著名脑外科专家。他回到虎纠小县城度假的一个夜晚,遇到几个流氓为难大排档卖唱的小妹。

黄金灯:年轻人,文明点!

流氓头:摸个奶,我给钱就是,要你管啊!

黄金灯:小姑娘,你愿意吗?

小姑娘:不!不愿意。

黄金灯:听到没?这位姑娘不卖身。

流氓头:你谁啊?大叔!

黄金灯:我是一名脑外科医生。知道小李飞刀吗?

流氓头:哦?李医生?还飞刀!哈哈哈……

黄金灯亮刀:我这几把叫小灯飞刀,比小李飞刀厉害。

流氓头:大叔,你是不是脑子有问题啊?兄弟们,教训一下他!

黄金灯一出手,飞刀准确插在后面两个流氓鞋头,避开脚趾,将鞋钉在地上,刀拔都拔不动。

流氓发现往前走脚就会被刀切,知道遇到高手,吓得不敢动。

黄金灯:来。我不用飞刀,截拳道对付你。

流氓头跪倒:大叔!小的不识抬举,跟您道歉了。

黄金灯:转过去,跟小姑娘道歉。

小姑娘:大叔怎么称呼?

黄金灯:道释·圣小开。

2003 年

陈因提:死开,你家的牌坊为什么写的是龙田氏?

圣小开:上面写的是:龙田圣心,无尽乾坤。

陈因提:瞎说,上面只有三个字!

圣小开:em?我怎么记得是八个字!

陈因提:又发神经?

圣小开:有可能……晚上安定后告诉你。

1994 年

圣小开出生在道州德国鹰熊岛乾坤村,从小就很好奇村外面是什么。

累积无数次冲动,开终于打算勇敢地往西走,穿过盐田,去看看树木后面是什么!

虽然不远,却感觉走了很久。终于走到树木后面,是一条公路,横穿公路后是一片海沙田,听大人说这田盛产地瓜和花生。

强行穿过海沙田,是小土坡,再翻过去是一些河和另一些盐田,再过去就是海。

开明白,一路向西是无法走到远处的高山。心想:“试试向北,看那片树林后面是不是还是海?”

向右转!走着走着就是一个庙,再过去是灵堂。开想了三秒钟,可怕……还是绕过去吧!

但是开太天真了,虽然绕过灵堂,但它附近有一大堆土坟!于是为了避开它们,居然绕迷路!

平时当开要遇到危险时,都会有路人甲乙冒出来提醒,这次居然没有?

咦?那块石头上面好像有字!走进一看,不禁念出上面的字:“龙田圣心,无尽乾坤”。好酷的感觉!

纯狐连玉:这界碑下面是仙山公。

开转身一看,是个黄色衣装的漂亮的姐姐,便问:仙山公是谁?

纯狐连玉:你干妈的父亲。

圣小开:哦?可是我拜了两位干妈,您说的是哪位?

纯狐连玉:是姑婆祖。

圣小开:哦!原来是族谱第一人。

纯狐连玉:赶紧拜一拜。

圣小开双手合十俯身朝拜:好的。敢问姐姐又是谁?

纯狐连玉:我是仙山公陵墓的守护兽,纯狐连玉。

圣小开:什么?胡连玉?

纯狐连玉:纯狐,连玉。

圣小开:好的,小玉姐姐。

纯狐连玉:死囡仔,按辈分我和你姑婆祖同辈。

圣小开:婆婆好!

纯狐连玉:我是狐狸精。你不害怕?

圣小开:狐狸精?我还是孙悟空转世呢!

纯狐连玉:不怕也好。你跪下,给仙山公磕三个头,我可以实现你一个愿望。

圣小开:这么好康?我要长生不老。

纯狐连玉:死囡仔,活那么久干嘛?三千年就不错啦!

圣小开:成交!我磕。

磕完头,纯狐连玉已经不见。

2003 年

陈因提:狐狸精都出来了!你真能做白日梦!

圣小开:我很严肃好不好!后来我还梦见过它,问它为什么选择我。

陈因提:它怎么说?

圣小开:说我是世间少有的能做很真实的连戏剧梦的人。

陈因提:好像很厉害的样子哦。但是这种能力有什么用?

圣小开:这种能力可以让我从梦中进入全人类,乃至全宇宙所有智慧生命的集体意识中。

陈因提:噗!然后呢?

圣小开:神不会救助任何个体,你懂吧?

陈因提:是啊,要不然神应该来治治你这惊人的幻想能力……

圣小开:不开玩笑,人类社会有很多问题不是在明面可以解决的,或者说这些问题也不需要从明面去解决,而应该通过观测集体意识从而影响它。

陈因提:好吧,你赢了,但是能具体点吗?

圣小开:很多问题的根本都是心灵问题。比如说,残疾人很敏感地发现别人看待他异样的眼光,这可能加强他对自己的嫌弃和遗憾。当你看到别人的问题,一个惊讶、困惑的眼神,对方可能解读成你看到他们差劲的一面,自觉得你会嫌弃他们,于是就烙下一个芥蒂。

陈因提:那你到底能做什么?

圣小开:我可以在睡着的时候进入集体意识去观测大家,让大家的意识明白不管好坏,神都不会遗弃他们。当我醒来世界就会更美好。

陈因提:那你好忙哦,赶紧睡……晚上来观测一下我的意识,看我的意识会不会打你。

圣小开:还好,像我这样的,地球上有 23 人。