你見過哪些令你瞠目結舌的C/C++代碼技巧?
update 2015/11/26
這個問題已經被引申為瞠目結舌的系列了,作為題主,我表示瞠目結舌。X:基友問題你見過哪些讓你瞠目結舌的JAVA代碼技巧? - Java你見過哪些令你瞠目結舌的 JavaScript 代碼技巧? - 程序員
你見過哪些令你瞠目結舌的前端設計? - 程序員你見過哪些令你瞠目結舌的Python 代碼技巧? - 編程 http://www.zhihu.com/question/38241342神經問題,非程序員歡迎一起來回答你見過哪些令你瞠目結舌的神邏輯? - 生活你見過哪些令你瞠目結舌的傻比? - 生活你見過哪些令你瞠目結舌的逗逼? - 生活------------------------------------------------------------------------------------------------------update 2015/11/223000個關注了,大家都挺好奇哈哈。還是希望能出現一些既實用又精巧的技巧,不要是#define TRUE FALSE這樣純屬惡作劇的東東~很多人說這樣的技巧純屬多餘,會影響閱讀性之類。其實對於某個輸入輸出非常確定,並且大部分人都明白原理的函數,如果能通過技巧去將速度提高數倍乃至數十倍,我覺得是非常了不起的,並且對於可讀性也沒有什麼影響,畢竟你只需要知道這個函數的輸入輸出這樣的映射就行了,內部怎麼實現的對於工程來說沒有什麼影響,當然這種函數應該是職責非常單一原子性的。比如答案中出現的一個通過位運算快速進行大小寫轉換的函數。--------------------------------------------------------------------------------------------------update 2015/11/21這裡不談可讀性之類的問題,只show出來純粹的令人驚奇的技巧。-------------------------------------------------------------------------------------------------- 有同學說下面這個例子太弱智,原諒樓主孤陋寡聞。。。
如果有優化經驗或者編譯器開發的同學能講一講在現代編譯器(msvc2013,gcc4.8,clang...)下哪些優化技巧純屬多此一舉的話就更好了。樓主舉個例子(類似switch轉查表):
void threshold(unsigned char* src,unsigned char* dst,int len,unsigned char thr)
{
for(int index=0; index &< len; index++) { if(src[index] &> thr)
{
dst[index] = 255;
}
else
{
dst[index] = 0;
}
}
}void threshold_optimized(unsigned char* src,unsigned char* dst,int len,unsigned char thr)
{
//optimize cache table
unsigned char tab[256];
int i =0;
for(; i &<= thr; i++) { tab[i] = 0; } for(; i &< 256; i++) { tab[i] = 255; } for(int index = 0; index &< len; index++) { dst[index] = tab[src[index]]; } }
寫幾個folly(Facebook開源的c++庫)裡面的例子
------四更,folly::future 是如何chain你的callback的 ------
folly::future 是一個酷炫屌炸天的庫,FB內部大量的非同步C++的代碼都是基於future的。我這段只講他怎麼支持下列語法的:
folly::makeFuture().then([]() {
// 返回一個數字 return 10;}).then([](int i) {// 如果寫 }).then([](string i) { 的話編譯器會報錯 // 但是寫 }).then([](Try&});
也就是說,callback B 是接在callbackA後面的話,callback A 如果返回的是 T,我們可以支持callback B接受 T, T, T, Try&
/** When this Future has completed, execute func which is a function that
takes one of:
(const) Try&
(const) Try&
(const) Try&
(const) T
(const) T
(const) T
(void)
Func shall return either another Future or a value.
A Future for the return type of func is returned.
Future&
The Future given to the functor is ready, and the functor may call
value(), which may rethrow if this has captured an exception. If func
throws, the exception will be captured in the Future that is returned.
*/
template &<
typename F,
typename FF = typename detail::FunctionReferenceToPointer&
typename R = detail::callableResult&
typename R::Return then(F func) {
typedef typename R::Arg Arguments;
return thenImplementation&
}
template&
struct callableWith {
template&
static constexpr std::true_type
check(std::nullptr_t) { return std::true_type{}; };
template&
static constexpr std::false_type
check(...) { return std::false_type{}; };
typedef decltype(check&
static constexpr bool value = type::value;
};
template&
struct callableResult {
typedef typename std::conditional&<
callableWith&
detail::argResult&
typename std::conditional&<
callableWith&
detail::argResult&
typename std::conditional&<
callableWith&
detail::argResult&
typename std::conditional&<
callableWith&
detail::argResult&
detail::argResult&
typedef isFuture&
typedef Future&
};
template&
using resultOf = decltype(std::declval&
這裡一大波template我們一個一個來。
FunctionReferenceToPointer
可以無視掉,你可以想像成FF就是F
std::declval 讓你把 F變成 F,所以可以用template&
using resultOf = decltype(std::declval&
這樣的語法拿到F(Args args...) 的返回值,不管F是object還是lambda。這樣,resultOf可以拿到我們的callback的返回type。現在我們得把這個返回值跟下一個函數的argument對應起來。這裡我們用callableWith
template&
struct callableWith {
template&
static constexpr std::true_type
check(std::nullptr_t) { return std::true_type{}; };
template&
static constexpr std::false_type
check(...) { return std::false_type{}; };
typedef decltype(check& 這裡check有兩個specialization,一個在編譯時候會返回true一個會返回false。注意只要不符合第一個specialization的都是false,也就是說resultOf沒有成功,check(nullptr) 就是false type。這個技巧叫做Substitution Failure Is Not An Error SFINAE - cppreference.com 。再配倒數第二行的typedef,如果 F可以接受args,那麼callableWith&
static constexpr bool value = type::value;
};
template&
struct callableResult {
typedef typename std::conditional&<
callableWith&
detail::argResult&
typename std::conditional&<
callableWith&
detail::argResult&
typename std::conditional&<
callableWith&
detail::argResult&
typename std::conditional&<
callableWith&
detail::argResult&
detail::argResult&
typedef isFuture&
typedef Future&
};
我們就可以在編譯時間確保我們可以支持我們所有想支持的7個類啦。那具體拿著第一個callback的返回值怎麼傳輸到第二個callback上面做argument呢?這一段變種太多,我只給大家看最簡單的變種:
// Variant: returns a value
// e.g. f.then([](Try&
template &
template &
typename std::enable_if&::type
Future&
static_assert(sizeof...(Args) &<= 1, "Then must take zero/one argument");
typedef typename R::ReturnsFuture::Inner B;
throwIfInvalid();
Promise& p;
p.core_-&>setInterruptHandlerNoLock(core_-&>getInterruptHandler());
// grab the Future now before we lose our handle on the Promise
auto f = p.getFuture();
f.core_-&>setExecutorNoLock(getExecutor());
// 這裡注釋省略,因為實在太長。
setCallback_([ funcm = std::forward&
Try&
if (!isTry t.hasException()) {
pm.setException(std::move(t.exception()));
} else {
pm.setWith([]() { return funcm(t.template get&
}
});
return f;
}
template &
template &
void Future&
throwIfInvalid();
core_-&>setCallback(std::forward&
}
protected:
typedef detail::Core&
// shared core state object
corePtr core_;
core_ 是future 的member,也就是說我們設置的callback僅僅只是被加到callback裡面去了,還沒有被執行。只有當你執行future.get() 的時候值才會被你拿到
template &
T Future&
return std::move(wait().value());
}
template &
Future&
detail::waitImpl(*this);
return *this;
}
template & 啊,所有的callback執行都被扔到baton裡面去了,我先不在這裡繼續深挖了。總之 auto value = folly::makeFuture(cb1).then(cb2).get(); - 在編譯的時候,是通過一系列declval/decltype/SFAINE/std::conditional 來確保類是對的
void waitImpl(Future&
// short-circuit if there"s nothing to do
if (f.isReady()) return;
FutureBatonType baton;
f.setCallback_([](const Try&
baton.wait();
assert(f.isReady());
}
--------三更,講點稍微實用一點的數據結構吧, 兩個看起來風馬牛不相及其實儲存上一致的folly::Optional 和 folly::Indestructible ------
folly::Optional
folly/Optional.h at master · facebook/folly · GitHub
c++裡面不是所有的類都可以是null的,特別有的時候這個類是其他人硬塞給你的。而當你需要它可是你null的時候,你把這個類放到folly optional裡面,它就可以是null啦。folly optional裡面比較有意思的是它儲存的機制 using Storage = typename std::conditional&
StorageTriviallyDestructible,
StorageNonTriviallyDestructible&>::type;
Storage storage_;
struct StorageTriviallyDestructible {
// uninitialized
uniocn { Value value; };
bool hasValue;
StorageTriviallyDestructible() : hasValue{false} {}
void clear() {
hasValue = false;
}
};
struct StorageNonTriviallyDestructible {
// uninitialized
union { Value value; };
bool hasValue;
StorageNonTriviallyDestructible() : hasValue{false} {}
~StorageNonTriviallyDestructible() {
clear();
}
void clear() {
if (hasValue) {
hasValue = false;
value.~Value();
}
}
};
StorageTriviallyDestructible 還稍微合理一點,StorageNonTriviallyDestructible.clear() 裡面~Value() 絕對很少見。這是c++11裡面unconstrained union的新玩法,因為在unconstrained union裡面你必須有能力可以銷毀一個non-POD類,所以c++語法開放了~Value() 這種語法,讓你可以銷毀這個值。這個功能在這裡就被弄出了新玩法,被用來支持folly::Optional.clear(),這樣就算是一個NonTriviallyDestructible的對象你也可以隨時銷毀它。
這種unconstrained union的啟動機制也是比較麻煩的。folly::optional.set 長這樣:
template&
void construct(Args... args) {
const void* ptr = storage_.value;
// for supporting const types
new(const_cast&
storage_.hasValue = true;
}
new(const_cast&
這個叫placement new,就是說你給new 一個地址,new直接在你給的地址上面initialize,而不是去heap裡面占內存。有上面兩個玩法的話,你就可以隨時隨地在c++啟動,銷毀這個值啦!
folly::Indestructible
如何確保你的meyer"s singleton永遠不死?這樣儲存你的類: union Storage {
T value;
template & 看起來好像沒有什麼特殊的對不對?不要忘記這個T在這裡肯定是個non trivially destructable的類。在這個union裡面,既然你的destructor是空的,那麼也就是說value永遠被遺忘了。。。遺忘了。。。遺忘了。。
explicit constexpr Storage(Args... args)
: value(std::forward&
~Storage() {}
};
看到這裡有人要開罵了,為什麼不直接new一個值出來,不銷毀就好了?這跟new一個新的值出來最大的差別是這個不可以被遺忘的值是可以被inline的,它用的內存不是heap裡面的內存(至少value本身不在heap上面)。這在效率上的差別是不可小覷的。
folly::optional 跟 folly::indestructable 都是利用了新標準裡面union的新特性。看來新玩法還是要多想
---------謝謝各位踴躍點贊,這段是二更。要是過一千的話我就寫MPMCQueue哦--------
folly::Conv 是可以把所有類轉化成所有類的庫。也不是所有啦,不過正常人用的到的都有。我這裡只講int轉字元串,float/double 實在太麻煩。先看數字轉字元串裡面算多少位數的/**
* Returns the number of digits in the base 10 representation of an
* uint64_t. Useful for preallocating buffers and such. It"s also used
* internally, see below. Measurements suggest that defining a
* separate overload for 32-bit integers is not worthwhile.
*/
inline uint32_t digits10(uint64_t v) {
#ifdef __x86_64__
// For this arch we can get a little help from specialized CPU instructions
// which can count leading zeroes; 64 minus that is appx. log (base 2).
// Use that to approximate base-10 digits (log_10) and then adjust if needed.
// 10^i, defined for i 0 through 19.
// This is 20 * 8 == 160 bytes, which fits neatly into 5 cache lines
// (assuming a cache line size of 64).
static const uint64_t powersOf10[20] FOLLY_ALIGNED(64) = {
1,
10,
100,
1000,
10000,
100000,
1000000,
10000000,
100000000,
1000000000,
10000000000,
100000000000,
1000000000000,
10000000000000,
100000000000000,
1000000000000000,
10000000000000000,
100000000000000000,
1000000000000000000,
10000000000000000000UL,
};
// "count leading zeroes" operation not valid; for 0; special case this.
if UNLIKELY (!v) {
return 1;
}
// bits is in the ballpark of log_2(v).
const uint8_t leadingZeroes = __builtin_clzll(v);
const auto bits = 63 - leadingZeroes;
// approximate log_10(v) == log_10(2) * bits.
// Integer magic below: 77/256 is appx. 0.3010 (log_10(2)).
// The +1 is to make this the ceiling of the log_10 estimate.
const uint32_t minLength = 1 + ((bits * 77) &>&> 8);
// return that log_10 lower bound, plus adjust if input &>= 10^(that bound)
// in case there"s a small error and we misjudged length.
return minLength + (uint32_t) (UNLIKELY (v &>= powersOf10[minLength]));
#else
uint32_t result = 1;
for (;;) {
if (LIKELY(v &< 10)) return resu<
if (LIKELY(v &< 100)) return result + 1;
if (LIKELY(v &< 1000)) return result + 2;
if (LIKELY(v &< 10000)) return result + 3;
// Skip ahead by 4 orders of magnitude
v /= 10000U;
result += 4;
}
#endif
}
const uint32_t minLength = 1 + ((bits * 77) &>&> 8);
這個就是亮點了。高中數學沒學好的話,這裡強調一下
log_10(v) =log_10(2) * log_2(v) (The Change-of-Base Formula)
&>&> 8 就是處以256bits在這裡已經是log(2)了+1是因為 0.3010略小於77/256我們繼續瞎,現在開始正式轉換
/**
* Copies the ASCII base 10 representation of v into buffer and
* returns the number of bytes written. Does NOT append a . Assumes
* the buffer points to digits10(v) bytes of valid memory. Note that
* uint64 needs at most 20 bytes, uint32_t needs at most 10 bytes,
* uint16_t needs at most 5 bytes, and so on. Measurements suggest
* that defining a separate overload for 32-bit integers is not
* worthwhile.
*
* This primitive is unsafe because it makes the size assumption and
* because it does not add a terminating .
*/
inline uint32_t uint64ToBufferUnsafe(uint64_t v, char *const buffer) {
auto const result = digits10(v);
// WARNING: using size_t or pointer arithmetic for pos slows down
// the loop below 20x. This is because several 32-bit ops can be
// done in parallel, but only fewer 64-bit ones.
uint32_t pos = result - 1;
while (v &>= 10) {
// Keep these together so a peephole optimization "sees" them and
// computes them in one shot.
auto const q = v / 10;
auto const r = static_cast&
buffer[pos--] = "0" + r;
v = q;
}
// Last digit is trivial to handle 這裡面開始甩說用pos慢了,估計原因是loop unrolling做不了,但是具體不好說,我得問問他。
buffer[pos] = static_cast&
return resu<
}
/**
* int32_t and int64_t to string (by appending) go through here. The
* result is APPENDED to a preexisting string passed as the second
* parameter. This should be efficient with fbstring because fbstring
* incurs no dynamic allocation below 23 bytes and no number has more
* than 22 bytes in its textual representation (20 for digits, one for
* sign, one for the terminating 0).
*/
template &
typename std::enable_if&<
std::is_integral&
IsSomeString&
toAppend(Src value, Tgt * result) {
char buffer[20];
if (value &< 0) {
result-&>push_back("-");
result-&>append(buffer, uint64ToBufferUnsafe(-uint64_t(value), buffer));
} else {
result-&>append(buffer, uint64ToBufferUnsafe(value, buffer));
}
}
好了我服了。。。
-----------------------原先的答案------------------------------
AtomicStructfolly/AtomicStruct.h at master · facebook/folly · GitHub類似於std::atomic, 但是任何小於8個byte的POD類都可以變成atomic的。實現的方法如下:用一個unconstrained union 來存數據:union {
Atom&
T typedData;
};
typename Raw = typename detail::AtomicStructIntPick&
template &<&> struct AtomicStructIntPick&<1&> { typedef uint8_t type; };
template &<&> struct AtomicStructIntPick&<2&> { typedef uint16_t type; };
template &<&> struct AtomicStructIntPick&<3&> { typedef uint32_t type; };
template &<&> struct AtomicStructIntPick&<4&> { typedef uint32_t type; };
template &<&> struct AtomicStructIntPick&<5&> { typedef uint64_t type; };
template &<&> struct AtomicStructIntPick&<6&> { typedef uint64_t type; };
template &<&> struct AtomicStructIntPick&<7&> { typedef uint64_t type; };
template &<&> struct AtomicStructIntPick&<8&> { typedef uint64_t type; };
我看到這裡已經開始瞎了。
compare exchange是這樣的 bool compare_exchange_weak(
T v0, T v1,
std::memory_order mo = std::memory_order_seq_cst) noexcept {
Raw d0 = encode(v0);
bool rv = data.compare_exchange_weak(d0, encode(v1), mo);
if (!rv) {
v0 = decode(d0);
}
return rv;
}
裡面的encode/decode就是拿來騙編譯器的memcpy。寫了這麼多廢話,說白了就是為了讓編譯器開心的可以用各種std::atomc&
DiscriminatedPtr
用法就是boost::variant,但是用DiscriminatedPtr沒有任何多餘的代價,就是一個指針的大小。為什麼可以沒有代價呢?應為64位系統裡面其實只有48位拿來做地址了,剩下16位是沒有被系統用起來的。所以要地址是這麼讀的 void* ptr() const {
return reinterpret_cast&
那前16個bit是存什麼呢?存的是現有這個類的index。每次存的時候,會通過index找到對應的類
/**
* Set this DiscriminatedPtr to point to an object of type T.
* Fails at compile time if T is not a valid type (listed in Types)
*/
template &
void set(T* ptr) {
set(ptr, typeIndex&
}
然後
void set(void* p, uint16_t v) {
uintptr_t ip = reinterpret_cast&
CHECK(!(ip &>&> 48));
ip |= static_cast&
template &
template &
struct GetTypeIndex&
static const size_t value = 1;
};
template & 具體實現在這裡folly/DiscriminatedPtrDetail.h at master · facebook/folly · GitHub。 這樣在編譯時間你就可以知道你要的類是不是這個指針支持的類。要是對編譯時的黑魔法感興趣的話,可以從boost的index_sequence看起 boost/fusion/support/detail/index_sequence.hpp
struct GetTypeIndex&
static const size_t value = 1 + GetTypeIndex&
};
!!! 多圖流量預警 !!!
Update Log––––––––––––––––––––––––––––––––––––––––––––––––––––––12/3/15- 重新用電腦排了版,之前手機寫的,發現版面好爛
- 修正求圓周率程序(KR vs ANSI C)
- 更新大量真·阿卡林軍團 炮姐程序
––––––––––––––––––––––––––––––––––––––––––––––––––––––
如果說「瞠目結舌」的話,IOCCC 上隨便拿一篇獲獎代碼出來就足以讓人下巴落地了。
The International Obfuscated C Code Contest
一個比較經典的例子是 1988 年得獎的代碼,這個程序直接估算字元面積求圓周率,可讀性算是比較友好的:
?westley.c?#define _ F--&>00||-F-OO--;
int F=00,OO=00;main(){F_OO();printf("%1.3f
",4.*-F/OO/OO);}F_OO()
{
_-_-_-_
_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_
_-_-_-_
}
註:這段程序實際上是 1989 年修正過的,由於 88 年原來程序代碼沒有考慮到 ANSI C 的編譯標準,導致在處理例如
#define _ -i
-_
的時候,老舊的 KR 框架和 ANSI C 結果不一樣:KR 是直接的
--i
而 ANSI C 編譯結果實際上等同於
-(-i)
因此之前的程序現在運行的話出來的結果是 0.250,而不是 3.141。修正過的程序就沒有這個問題了。
又比如 13 年有個只有一行的程序,可以判斷從 Franklin Pierce 往後的 31 位美國總統是民主黨還是共和黨,這個就有點不知所云了:
?cable1.c?main(int riguing,char**acters){puts(1[acters-~!(*(int*)1[acters]%4796%275%riguing)]);}
食用方法:
make cable1
./cable1 obama republican democrat
./cable1 bush republican democrat
總統名要小寫,republican 和 democrat 順序不能顛倒。
經 @chua zier 提醒,歷史上的確有重名的美國總統,除了 Johnson 之外,還有 Theodore Roosevelt / Franklin D. Roosevelt,程序原作者註明用./cable1 roosevelt republican democrat
表示 Theodore Roosevelt,而用
./cable1 fdr republican democrat
表示 Franklin D. Roosevelt。
這一行代碼做了這麼多事:首先查詢輸入的總統的名字,然後在一個 look-up table 裡面找出對應的政治陣營,再輸出出來。問題在於這 31 位總統名字存放在哪裡?而這個 look-up table 又存放在哪裡?
有趣的是,IOCCC 的評委還提到,你甚至可以用這個程序檢測一些 IT 大佬的 Mac / PC 陣營: ./cable1 Cooper Mac PC
./cable1 Noll Mac PC
./cable1 Broukhis Mac PC
./cable1 Jobs Mac PC
./cable1 Gates Mac PC
./cable1 Ballmer Mac PC
難道這個程序暴露了 Ballmer 離開微軟的真相?
最近幾屆比賽的代碼為了增加混亂程度,代碼越來越長,可讀性也越來越差(不過話說回來,讓可讀性變得越來越差其實原本就是這個比賽的第一宗旨吧),不少代碼甚至本身就是個 ASCII artwork……比如 11 年有一隻阿卡林:
?akari.c?
為了保持美觀我就直接上圖了。源代碼見此:http://www.ioccc.org/2011/akari/akari.c–––––––––– !!! 前方阿卡林軍團高能預警 !!! ––––––––––
這個阿卡林程序實際上是一個圖像 down-sampler,可以接受符合條件的 PGM / PPM 灰度圖像或者 LF 換行(不支持 CR-LF)的 ASCII art 為輸入,然後轉換輸出一個處理後的圖像 / ASCII art。不過這個阿卡林最逆天的地方在於,它可以用自身的源代碼文本作為輸入,輸出生成另一個可以編譯運行的程序的代碼!而且把這個生成的程序文本繼續作為輸入做進一步 down-sample,又可以生成一段可以編譯的代碼,如此反覆,可以套多達4層!詳細的食用方法如下:make akari
./akari akari.c akari2.c
然後生成的阿卡林·2號是這個樣子的:
?akari2.c?看不清?請摘下眼鏡或者退遠了看。注意,阿卡林·2號也是可以編譯運行的,她的功能是把輸入的 ACSII 文本的每個字元中間插入空格以及每行之間插入空行,生成一段「疏鬆」了的文本。我們用阿卡林·2號自己做實驗品:make akari2.c
./akari2 & akari2fat.txt
成功了!生成了一隻阿卡林·2號·舒松:
?akari2fat.txt?阿卡林·2號還能幹別的,她支持一個 rot13 參數:./akari2 rot13 & akari2fat.txt
生成的是經過 ROT13 仿射變換的文本,我們稱之為阿卡林·2號·舒松·加蜜吧!
但是還沒完……如果我們把原版阿卡林放進去再來一層呢?./akari &< akari.c | ./akari &> akari3.c
於是阿卡林·3號誕生:
?akari3.c? wm_aoi(n)
/*ity,,[2*/{}char*y=
(")M{lpduKtjsa(v""YY"
"*yuruyuri") ;main(/*
/",U/ R)U* Y0U= ="/
*/){puts (y+ 17/*
"NR{I=" ){/=*
=* */);/*
**/{ ;;}}
可憐的阿卡林·3號,由於「馬賽克」(down-sample)次數太多,摘了眼鏡也只能模糊看到一點點……我們來問問阿卡林·3號對於誕生的感受吧:
make akari3
./akari3
於是她回答:
yuruyuri
居然會說ゆるゆり!
最後,我們嘗試生產一下阿卡林·4號:./akari &< akari.c | ./akari | ./akari &> akari4.c
main
(){puts("Y"
"U RU YU "
"RI" )/*
*/ ;}
順利生產!雖然內容已經直截了當了,不過我們還是採訪一下她吧:
make akari4
./akari4
她的答覆是:
YU RU YU RI
至此,阿卡林軍團全部誕生!
不得不佩服作者構建代碼的精妙程度。他的個人主頁在這裡:uguu... (這位作者其實已經是這比賽的常客了,先後一共拿過 6 次不同的獎項。)經 @馬琦明 提醒,我又把上面這位作者的另一個作品搬出來了,13 年的 Most Catty——炮姐程序。這程序的代碼長這個樣子:
?misaka.c?
源代碼:http://www.ioccc.org/2013/misaka/misaka.c對的,當你看到原來是這個「御坂」的時候,你就知道,我們要開始造(kè)人(lóng)了……make misaka
這個御坂的作用是把輸入的 ASCII 橫向連接起來。首先連接兩個自己試試:
./misaka misaka.c misaka.c &> misaka2.c
「把兩個御坂輸入一個御坂,會生成什麼?」「兩個御坂。」
?misaka2.c?聽起來很不可思議但是在這位作者的構建下完全不出意外地,上面這個御坂-2 居然也是可以編譯運行的:make misaka2.c
御坂-2 的功能是把輸入的 ASCII 縱向連接起來。那我們就試著縱向連接兩個御坂:
./misaka2 misaka.c misaka.c &> misaka3.c
於是御坂-3 誕生了:
?misaka3.c?
我們來運行一下這個御坂-3。你此時腦中的景象可能是這樣的:但是你錯了,御坂-3 會給你造出來更加精神污染的那隻 long cat:make misaka3
./misaka3
./misaka2 misaka.c misaka.c misaka.c misaka.c &> misaka4.c
make misaka4.c
./misaka4
那麼御坂-4 會給你造一隻更長的 looooong cat:
按作者的意思,你可以最多疊加 31 個御坂來生成一隻 looo....ooong cat(具體上限由編譯器的 sizeof(int) 決定)。13 年還有浙大教授侯啟明寫的 ray tracer 程序,雖然代碼本身存在爭議是否符合比賽規則,例如為避免長度超限制而使用了一些壓縮方法、程序是個死循環。如果這段程序可讀性不是這麼噁心的話其實還是非常值得鑽研的,裡面用到了很多有趣的數據結構和著色體系。
食用方法也很簡單,把程序掛在那兒跑一晚上,強制退出,就可以看結果了。由於是無窮盡的遞歸,程序跑的時間越長,圖像就越精緻。詳細的說明和源文件還是參考官網吧:
Previous IOCCC Winners with spoilers
這裡有個示例圖。
侯老師還有另外三個作品上榜,一個是極其酷炫的 syntax highlightener,還有一個(源代碼本身就是 GUI 的)科學計算器,後面這個已經有人@paintsnow回答過了。最新一個是上個月剛剛公布的新一屆獲獎作品 MD5 without integers,但是這個的源碼還沒有公布,估計要等到明年了。
有時間(有人看)的話我再繼續補充…… IOCCC 逆天的東西還有很多,例如 IBM 電腦模擬器,飛行模擬模擬器,各種遊戲等等等等,不得不佩服這些 geek 對 C 語言熟稔之極。看輪子哥寫了個關於lambda序列化的,我也準備寫個相關的東西。
1. 先寫個C++反射的吧。
這個反射主要好用在不需要用戶先去進行任何註冊操作。
Note:剛發現似乎只有在gcc上才不需要註冊(哎喲,瞬間感覺不高端了)。
因為gcc類內的靜態變數在main之前都構造好了,而有的編譯器是在第一次使用前才構造。而標準里寫了:
It is implementation-defined whether the dynamic initialization of a non-local variable with static storage duration is done before the first statement of main.
確實是每種編譯器自己實現決定的。
#include &
#include &
#include &
#include &
#include &
template&
class Reflection {
public:
template&
static std::string type_name() {
return TypeName&::type_name();
}
template&
static std::string type_name(Sub) {
return TypeName&::type_name();
}
static Base* create(const std::string name) {
return (*s_creator[name])();
}
private:
class AllocatorBase {
public:
virtual Base* operator()() = 0;
};
template&
class Allocator : public AllocatorBase{
public:
T* operator()() {
return new T;
}
};
template&
class Registery {
public:
Registery() {
s_creator[get_name()] = new Allocator&(); // 簡化版本,請忽略這個內存泄漏
}
static std::string get_name() {
// 實際上為避免引入rtti,我們用的是一個沒使用rtti的gcc方言搞定的,這裡為方便說明簡化成這樣
return typeid(Sub).name();
}
};
template&
class TypeName {
public:
static std::string type_name() {
return s_registery.get_name();
}
private:
static Registery& s_registery;
};
static std::map&
};
template&
std::map&
template&
template&
Reflection&
class MapFn {
public:
virtual void call() = 0;
};
class UserMapFn : public MapFn {
void call() {
puts("User Map Fn called");
}
};
int main(int argc ,char** argv)
{
if (argc == 1) {
std::string cmd = "";
cmd += argv[0];
cmd += " "" + Reflection&
std::cout &<&< cmd &<&< std::endl;
system(cmd.c_str());
return 0;
}
std::cout &<&< "in sub process" &<&< std::endl;
std::string name = argv[1];
Reflection&
}
我們的項目目的是實現一個分散式計算的表示層API,有了上面的反射,這個API用起來就可以長得像下邊這個樣子了(現實要比這個好看不少,這裡把功能簡化簡化再簡化了):
class ToInt: public MapFn&
int map(std::string input) const {
return boost::lexical_cast&
}
};
class Sum: public ReduceFn&
int reduce(int a, int b) const {
return a + b;
}
}
int main(int argc, char argv) {
init_workflow(argc, argv);
PCollection&
p.map(ToInt())
.reduce(Sum())
.writelines("./output_dir");
run_workflow();
}
然後在client端把這些要執行的函數記錄下來,翻譯到分散式系統上,再到分散式系統上正確的位置反射出來正確的函數並執行即可。
2. 關於lambda序列化。 // TODOC++普通類型的序列化,可以實現一些序列化類,派生自這個類:template&
class Serde {
public:
typedef T ObjectType;
virtual ~Serde() {}
virtual T* construct(char* buffer, int buffer_size) const {
CHECK_GE(buffer_size, sizeof(T));
return new(buffer) T();
}
virtual void destruct(T* ptr) const {
ptr-&>~T();
}
// Return the size of serialized object,
// The method is considered as failed when the size is larger than the buffer_size
virtual uint32_t serialize(const T object, char* buffer, uint32_t buffer_size) const = 0;
// The buffer_size is same as object serialized size
virtual void deserialize(T* object, const char* buffer, uint32_t buffer_size) const = 0;
// for debug
virtual std::string to_string(const T object) const = 0;
};
然後,弄一個SerdeOf,用來獲取一個類型默認的序列化器:
template&
struct SerdeOf;
然後把各種常見類型都註冊到SerdeOf上,例如:
template&
class ProtobufSerde : public Serde&
public:
virtual uint32_t serialize(const T message, char* buffer, uint32_t buffer_size) const {
google::protobuf::io::ArrayOutputStream stream(buffer, buffer_size);
if (message.SerializeToZeroCopyStream(stream)) {
return stream.ByteCount();
}
return message.ByteSize();
}
virtual void deserialize(T* message, const char* buffer, uint32_t buffer_size) const {
CHECK(message-&>ParseFromArray(buffer, buffer_size));
}
virtual std::string to_string(const T message) const {
return message.DebugString();
}
};
template& 這樣,就實現了一套可以擴展的且原生、常見類型都不需要用戶設置序列化器的序列化機制。
struct SerdeOf&
&>::type&> {
typedef ProtobufSerde&
};
用戶的Fn如果有狀態,則可以讓它派生自一個Protobuf的Message(或者自己實現一個serde,並註冊一下),這樣就可以在client端把Fn序列化,然後到遠端分散式執行時,把Fn反序列化回來。
普通的類都可以這樣搞定。
但只有普通的類型,並不能滿足我們。
於是,我們試著找一個能夠直接把lambda和原生函數序列化、反射、並反序列化的方法,這裡是找到的一個將lambda反射的方法,但種方法只能用在沒有捕獲變數的情況下,也就是必須無狀態。template&
class LambdaMapFn : public MapFn {
void call() {
//這樣構造一個lambda是經xreborner指點之後我才意識到可以這樣搞的。
(*static_cast&
}
};
template&
LambdaMapFn&
return LambdaMapFn&
}
int main(int argc ,char** argv) 如果捕獲變數都是POD類型,則可以直接像 @vczh輪子哥那樣memcpy一下,到遠端強轉一下,把那塊內存強轉成lambda,然後調用就可以了。 如果捕獲變數是non-POD類型,有兩個方法,但是那兩個方法也不是很完美。
{
if (argc == 1) {
std::string cmd = "";
cmd += argv[0];
cmd += " "" + Reflection&
std::cout &<&< cmd &<&< std::endl;
system(cmd.c_str());
return 0;
}
std::cout &<&< "in sub process" &<&< std::endl;
std::string name = argv[1];
Reflection&
}
總之,有了這些,前述的API介面就可以變成這樣了:
int main(int argc, char argv) {
init_workflow(argc, argv);
PCollection&
p.map([](std::string n) {return boost::lexical_cast&
.reduce([](int a, int b) {return a + b;})
.writelines("./output_dir");
run_workflow();
}
實際工程代碼基本是清晰第一,效率第二的,所以下面大多數奇技淫巧適用的場景大部分是ACM之類的
1.超快速讀入
現在快速讀入已經不算黑科技了吧,下面這個讀入在hdu上面速度比快速讀入快const int BUFSIZE=120&<&<20;char Buf[BUFSIZE+1],*buf=Buf;template&fread(Buf,1,BUFSIZE,stdin);
2.擴棧,系統棧在windows下有限,爆了怎麼辦?
#pragma comment(linker, "/STACK:102400000,102400000")3.vim寫代碼用到c++11的特性老是要編譯的時候加上-std=c++11,直接在程序上加:
#pragma GCC diagnostic error "-std=c++11"4.同樣類型的黑科技,程序裡面開O3優化__attribute__((optimize("-O3")))
5.09年駱可強論文中提到的各種黑科技:
為了消除分支預測,可以把絕對值寫成:inline int abs(int x)
{
int y = x &>&> 31;
return (x + y) ^ y;
}
inline int max(int x, int y)
{
return y((x-y)&>&>31)|x~((x-y)&>&>31);
}
int average = (x y) + ((x ^ y) &>&> 1)
為了使cache命中率更高,盡量避免2的冪次方長度的數組
6.c/c++里大括弧{ }可以和&<% %&>互換..........並不知道這有什麼意義
7.在某些場合使用以下代碼估計很多人已經熟悉了,但第一次看到下面代碼還是費解了半天#define st(x) do { x } while (__LINE__ == -1)
#define LLD "%lld"
比如輸出就可以printf("output: "LLD"
", x);
long long multiply(long long x,long long y,long long p)// x * y % p
{
long long ret = 0;
for(; y; y &>&>= 1)
{
if(y 1)ret = (ret + x) % p;
x = (x + x) % p;
}
return ret;
}
我在日本留學的時候看過一本日本人寫的書,書名叫 Short Coding:職人達の技法。顧名思義,講如何把代碼寫得儘可能短(可移植性,可讀性什麼的統統狗袋)。一本書都是奇技淫巧,簡直是裝逼利器,書里還有各種論述教你如何裝逼,例如(我實在是懶的抄書了,在中文版的書里截個圖不算侵權吧)
裝逼的正確姿勢當別人說你的代碼讀不懂的時候請這麼反駁當別人說你做事沒有意義的時候:好了,讓我們看看書里的代碼吧(為了方便理解,加入了換行)計算十二個數的平均值快速排序輸出第n個斐波那契數列的後四位好了以上都是比較簡單的例子啦,來個複雜點的感興趣的請自行google 短碼之美:編程達人的心得技法O(1)時間反轉二叉樹。
struct NormalNode {
int value;
struct NormalNode *left;
struct NormalNode *right;
};
struct ReversedNode {
int value;
struct ReversedNode *right;
struct ReversedNode *left;
};
struct ReversedNode *reverseTree(struct NormalNode *root) {
return (struct ReversedNode *)root;
}
玩內存結構高手...
出自: It can"t be just mirroring, because there"s the obvious zero-op solution because...-------- Update --------
不知道哪位大神贊了答案, 突然評論多了起來...題目問的是瞠目結舌么, 我覺得這段代碼對於C編譯後的內存結構需要有很好的理解才能寫得出來. 2個struct裡面的聲明順序必須一致, 才能這麼寫. 而且這種思考方式確實非常贊!另外說下有沒有用: 大部分時間裡沒什麼用.
但是, 任何數據結構, 都有它自己的強項和弱點. 計算機的藝術就是在trade off. 在需要大量的反轉二叉樹的場景中, O(1)時間要比任何演算法都有優勢. 那它的劣勢: 需要封裝, 客戶代碼複雜, 就通通不是劣勢. 是否要採用這種方式實現二叉樹, 取決於人, 而不是演算法.#define ture true
#define mian main/*都是大神,好厲害…我抖個機靈就跑*/一切通過位操作實現的演算法(奇技淫巧)都讓我瞠目結舌。
比如:
尋找只出現1次的一個數,其他數出現偶數次(或尋找唯一一個出現奇數次的數,其他數出現偶數次)。推薦一個位操作相關問題的合集:
Bit Twiddling Hackshttps://graphics.stanford.edu/~seander/bithacks.html題主舉的那個例子蠻好玩的。我也很好奇哪來那麼多答主能在不知道代碼應用場景的情況下上來拍題主。
那個例子推廣一下,可以推廣成在數據精度足夠低(比如byte)的時候,所有的運算都可以查表進行。所以256色遊戲經常會有查表實現各種圖層混合模式的奇技淫巧,比如你兩個顏色混合,實際上肯定是寫成new_color = some_blending_mode[fg_color][bg_color]這樣……
stackoverflow上鄙視的也是premature optimization,不是「已經知道了這個循環就是熱點而且這個代碼也需要性能但是就是嫌優化了以後代碼會變醜所以見死不救」,你們能區別一下么……貼一個自己以前寫的,用位運算來做大小寫轉換,一次轉換八個字元,超快
#include &
#include &
#include &
void fast_to_upper(const char *src, char *dest, size_t len) {
size_t i = 0;
const uint64_t *src_data = (const uint64_t*) src;
uint64_t *dest_data = (uint64_t*) dest;
size_t blocks = len / sizeof(uint64_t);
uint64_t src8, a, b;
unsigned char src_char, a_char, b_char;
for (; i &< blocks; i++) {
src8 = src_data[i] 0x7f7f7f7f7f7f7f7f;
a = src8 + 0x0505050505050505;
b = src8 + 0x7f7f7f7f7f7f7f7f;
a = a b;
a = a (a &>&> 1) 0x2020202020202020;
dest_data[i] = src8 - a;
}
i *= sizeof(uint64_t);
for (; i &< len; i++) {
src_char = src[i] 0x7f;
a_char = src_char + 0x05;
b_char = src_char + 0x7f;
a_char = a_char b_char;
a_char = a_char (a_char &>&> 1) 0x20;
dest[i] = src_char - a_char;
}
dest[i] = 0;
}
int main() {
char dest[1024];
const char *src = "Hello World!!";
fast_to_upper(src, dest, strlen(src));
printf("%s
", dest);
return 0;
}
補充說明:
首先說明,ascii 碼中,小寫轉寫字母只需降二進位種的第 5 位置 0 即可,比如 A 的二進位是 01000001 而 a 的二進位是 01100001小寫字母的取值範圍是 01100001 到 01111010,也就是說,只有 "a" 到 "z" 滿足下面的條件:1. x - 1 的二進位數值是 011xxxxx2. x + 5 的二進位數值是 011xxxxx我們來看後面單循環的版本吧。
1. 首先先對字元 0x7f 把最高位消除,防止進位造成問題。2. a 就是對原本的字元進行 +53. b 就是對原本的字元進行 -1 ( +0x7f 等同於 -1 )4. a = ab,根據上面所說,ab 的開頭是 011 的就說明是小寫字母,否則則不是5. a (a &>&> 1),如果是小寫字母的話必定開頭是 001,否則則不是小寫字母。過後再 0x20 來把其他位都置空。最終得到的數值,如果原本字元是小寫字母,則得到 0x20,否則為 06. 開始說的,小寫字母的 ascii 碼減 0x20 就能得到大寫字母了有人懷疑性能,我來補充 benchmark:char lowercase_to_uppercase_map[256];
void init_map() {
for (int i = 0; i &< 256; i++) {
if (i &<= "z" i &>= "a") {
lowercase_to_uppercase_map[i] = i - "a" + "A";
} else {
lowercase_to_uppercase_map[i] = i;
}
}
}
void to_upper_by_map(const char *src, char *dest, size_t len) {
for (int i = 0; i &< len; i++) {
dest[i] = lowercase_to_uppercase_map[src[i]];
}
dest[len] = 0;
}
#include &
#include &
void benchmark() {
int size = 1 &<&< 20;
char *src = (char *) malloc(size);
char *dest = (char *) malloc(size);
for (unsigned int i = 0; i &< size - 1; i++) {
src[i] = (i*29327)%93 + "!";
}
src[size] = 0;
init_map();
struct timeval tv_start;
struct timeval tv_end;
int usec_passed;
gettimeofday(tv_start, NULL);
for (int i = 0; i &< 1000; i++) {
to_upper_by_map(src, dest, size - 1);
}
gettimeofday(tv_end, NULL);
usec_passed = (tv_end.tv_sec - tv_start.tv_sec) * 1000000 + (tv_end.tv_usec - tv_start.tv_usec);
printf("Method 1: %d us
", usec_passed);
gettimeofday(tv_start, NULL);
for (int i = 0; i &< 1000; i++) {
fast_to_upper(src, dest, size - 1);
}
gettimeofday(tv_end, NULL);
usec_passed = (tv_end.tv_sec - tv_start.tv_sec) * 1000000 + (tv_end.tv_usec - tv_start.tv_usec);
printf("Method 2: %d us
", usec_passed);
}
實際測試:(我本機測試,2014年底的 rMBP,具體 spec 就不貼了)
Method 1: 3280091 usMethod 2: 687517 us性能大概差四倍多吧這個應該算:Duff"s devicevoid memcpy (register char *to, register char *from, register int count) { register int n = (count+7)/8; switch (count % 8) { case 0: do { *to++ = *from++; case 7: *to++ = *from++; case 6: *to++ = *from++; case 5: *to++ = *from++; case 4: *to++ = *from++; case 3: *to++ = *from++; case 2: *to++ = *from++; case 1: *to++ = *from++; } while(--n&>0); } } 可能很多人沒看懂這段代碼在幹什麼,其實這段代碼就是在做很簡單的內存拷貝。我整理了一下代碼。代碼利用了編譯器實現switch-case語法時的特性,使得多個複製操作被連續執行,在具有流水線的CPU上面,可以最大限度地填滿流水線,從而增加拷貝的效率。其實這段代碼有很多變種,例如,可以選擇不同的case數量,不一定是8,以取得更好的效率。讀/寫的目標也不一定是內存。而且某些情況下這個方法性能不是最好的。先批量複製8的倍數個然後再複製剩下的有時性能更高。其實如果是我自己用,我一般會選擇後一種方法,之所以把這段代碼貼出來,主要是因為第一次見到這種混合switch-case和do-while的用法時,對我的衝擊性很大。另外有回復說這段代碼性能不怎麼樣,我真的很好奇,能否提供實現相同功能的純粹的C代碼?請不要提供內嵌彙編的代碼。
(在某些情況下)超快速的double轉int :
inline int double2int(double d)
{
d += 6755399441055744.0;
return *(int*)(d);
}
直接利用ieee浮點運算的規則和double到int的天然截斷完成轉換,
耗時僅需一個double加法.Casper"s Device:
(from Casper"s Device - 優秀的Free OS(Linux)版 - 北大未名BBS)
Casper"s enumeration:
寫幾個我自己用到過的:1. switch case達到goto的效果:
void escape(uint8_t byte, uint8_t* out, int out_limit=3){
switch(out_limit){
case 3:
out[2]=(uint8_t)(ESCAPE_MAP[byte3]?1:0|((byte3)&<&<1));
case 2:
out[1]=(uint8_t)((byte&>&>2)7);
case 1:
out[0]=(uint8_t)((byte&>&>5)7);
}
}
template &
class deferred_action {
Func _func;
bool _cancelled = false;
public:
deferred_action(Func func) : _func(std::move(func)) {}
~deferred_action() { if(!_cancelled)_func(); }
void cancel() { _cancelled = true; }
};
template & 3. 通過placement new實現無需動態分配內存的類似Scala中的Option:
inline deferred_action&
return deferred_action&
}
template&
using Tp=std::tuple&
template&
static std::tuple&
return std::make_tuple(arr...);
}
template&
struct _match_Tp_helper{
static void _match(const Tuple tp, const F func);
};
template&
struct _match_Tp_helper&
static void _match(const Tuple tp, const F func) {
func(std::get&<0&>(tp), std::get&
}
};
template&
struct _match_Tp_helper&
static void _match(const Tuple tp, const F func){
return _match_Tp_helper&
}
};
template&
static void match_Tp(const std::tuple&
const int tplen=sizeof...(T);
_match_Tp_helper&
}
去看看p99( http://p99.gforge.inria.fr )。p99可以做很多事,比如最著名的是讓 C 函數擁有默認參數,就像 C++ 函數一樣(http://stackoverflow.com/questions/10267269/how-to-tell-if-an-optional-argument-was-passed-to-a-function-c)。
substring("hello", 2, 2);substring("hello", 2);他甚至還能讓 C 函數做到這樣
substring("hello", , 2); // 省略中間的參數使用它很簡單,但是你若看一眼他的實現,呵呵。絕對的geek。while (T --&> 0)T趨近於0
我們單位招的很多剛來的小年輕寫的代碼經常看的我瞠目結舌。。。
那肯定是鼎鼎大名的InvSqrt了,用來快速計算平方根的倒數。
float InvSqrt(float x)
{
float xhalf = 0.5f*x;
int i = *(int*)x; // get bits for floating value
i = 0x5f3759df - (i&>&>1); // gives initial guess y0
x = *(float*)i; // convert bits back to float
x = x*(1.5f-xhalf*x*x); // Newton step, repeating increases accuracy
return x;
}
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(以下摘自0x5f375a86_百度百科)
這是一個傳奇演算法,此演算法最早被認為是由約翰·卡馬克所發明,但後來的調查顯示,該演算法在這之前就於計算機圖形學的硬體與軟體領域有所應用。它的速度要比標準的牛頓迭代法快上 4 倍,而其中的關鍵是一行神秘的代碼和一個莫名其妙的數字0x5f3759df。普度大學的數學家Lomont覺得很好玩,
決定要研究一下卡馬克弄出來的這個猜測值有什麼奧秘。Lomont也是個牛人,在精心研究之後從理論上也推導出一個最佳猜測值,和卡馬克的數字非常接近, 0x5f37642f。卡馬克真牛,他是外星人嗎?傳奇並沒有在這裡結束。
Lomont計算出結果以後非常滿意,於是拿自己計算出的起始值和卡馬克的神秘數字做比賽,看看誰的數字能夠更快更精確的求得平方根。結果是卡馬克贏了...誰也不知道卡馬克是怎麼找到這個數字的。
最後Lomont怒了,採用暴力方法一個數字一個數字試過來,終於找到一個比卡馬克數字要好上那麼一丁點的數字,雖然實際上這兩個數字所產生的結果非常近似,這個暴力得出的數字是0x5f375a86。Lomont為此寫下一篇論文,Fast inverse square root (見wikipedia)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
有的同學說運行結果不對,於是本著嚴謹治學的原則,我又試驗了一次。
(結果當然不同。。。但是很相似。。。_(:зゝ∠)_環境:#define private public
向各位瞠目結舌界的朋友推薦:
Highest Voted "c++" Questions如果想在瞠目結舌C++的道路上走得更遠:
Advanced C++ Programming Styles and IdiomsScientific and Engineering C++: An Introduction with Advanced Techniques and ExamplesC++ Black BookRuminations on C++: A Decade of Programming Insight and Experience已經有朋友看到了瞠目結舌的巨大潛力,所以其他語言的也一併奉送了
Highest Voted "c" QuestionsHighest Voted "java" QuestionsHighest Voted "python" QuestionsHighest Voted "php" QuestionsHighest Voted "javascript" Questions我保證你能從這些鏈接里找到許多瞠目結舌的東西。推薦閱讀:
※gcc為何有時會在call前push eax,默認的調用約定不是cdecl?
※現代C/C++編譯器有多智能?能做出什麼厲害的優化?
※參加2017年在新浪舉辦的北京場「前端體驗大會」是個什麼樣的體驗?