首页 >> 心理 >> 计算机科学 >> 文章

就有这么凑巧的事:上周还在看侯世达 (Hofstadter)先生的《集异璧》,这周就在电影院里看到了书中概念的演绎版——哈,说的当然是日前火爆酷炫的高分电影Inception啦。废话少说,赶快剧透。还没看过的同学们海涵了,虽说透穿了剧情也不会影响你观影的效果。

Inception抓人眼球的是它“梦中做 梦”和盗梦的情节设计。在层层深入的梦境里,人的意识逐步放松警惕,入侵者便可以趁机盗走储存在大脑中的信息。控制梦中的意识,“我做你的梦”,两个 人的思想在同一个大脑中争斗……这都是令人看得过瘾的狂野想象。然而身为一个业余程序员和《集异璧》忠实读者,看到人在梦里死了掉进迷失域(limbo) 再也出不来,我第一反应还是忍不住叫出来:“哇,堆栈溢出!”

堆栈是一个计算机术语,我看来看去,觉得Cobb这群人在剧中完成任务的方法就像出自程序员的手笔。你看每个梦,都是同样的一组人物,抱着同样的目标,只是换到了不同的场景里。这多像一个函数调用的过程啊。

也许你没听说过函数调用,但你也许炒过青菜。 通常我们会先热锅、放油,然后爆炒、加盐、出锅。那么从热锅到出锅的一整套动作就可以写一个名为“炒”的函数。如果我们为白菜调用这个函数,就完成了“炒 白菜”的任务;如果为空心菜调用这个函数,就完成了“炒空心菜”这个任务。你还可以自由发挥,为各种包菜、韭菜、胡萝卜调用同一个函数,就把它们都炒了。

Cobb先生当然不在乎炒的是什么菜,他的任 务是在Fisher的脑中播种下拆分公司的念头。他为Fisher先生设计的函数就是梦,让Fisher的潜意识瓦解的梦。在计算机程序中,一个函数内部 可以调用另一个函数,在第二层函数运行的过程中,第一层的函数就在等待,直到第二层函数返回了运算结果,第一层函数再利用这个返回的值来继续它自身的运 算。这么一比较,Cobb的精心设计实际上就是用一个梦去调用另一个梦,上一层梦境中熟睡的人们都在等待下一层梦境中的人完成任务。一旦成功,就用音乐或失去平衡的方式返还(在迷失域则是死亡),来结束上一层梦境。

就像程序员喜欢在函数中调用函数来使问题步步细化,这些嵌套在一起的梦也起到了步步逼近Fisher内心深处的作用。但是这样层层的调用也有个风险,万一信息链被破坏,函数不知道自己身处的是哪一层,事情就要乱套。这样就使得“堆栈”这个概念变得重要了。

在计算机语言里,“栈”是内存中的存储区,它保存着正在运行中的程序的临时信息,在程序完成后就被新的程序信息覆盖。“堆栈”就是向这些存储区写入信息,好让系统知道现在哪个函数在等待返回值,以及返回来的值要到哪里去读取。

但是计算机的内存容量是有限的。当函数调用的 层数过多,新调用的函数信息在写入内存的时候空间不够,就把一些老的信息覆盖了。麻烦的是,被覆盖的那层函数还在等待下层函数的返回值来完成暂停的任务。 这样虽然新的函数成功运行,老函数却没法正确找到返回值,整个程序就出错了。这种因为空间不够而产生的错误覆盖,就是开头提到的堆栈溢出。

这和情节有什么联系呢?当然有啊,Cobb的 老婆Mal不就是堆栈出错的受害者嘛。根据影片情节设计,Mal在迷失域中就因为待得太久而失去了对现实的记忆。她一直在最底层的函数里,却认为活在最顶层的现实,不需要返回到任何地方。而Cobb的“栈”还是完好的,他还记得 现实中的孩子,知道应该返回到顶层去。

这个时候,如果Cobb直接带着Mal卧轨, 不和她说那些有的没的,两人也许就安全地从底层返还了。但是Cobb犯了个错误,他对Mal的潜意识进行了修改,这就相当于故意在Mal的“栈”里放置了 错误的但是有意义的数据。这样当Mal回到最顶层的现实时,本来整个大程序应该宣告结束,但因为她的“栈”被改写了,这个程序就错误地认为自己并不在顶 层。于是Mal就失去了对梦境和现实的分辨力,觉得自己应该再死一次才回到现实。

“悲剧啊!”看见Mal坠下高楼,我不由叹息,一出内存出错的惨剧。

与其说这是一部关于梦的科学幻想,倒不如说是 利用人类的算法对意识进行的一次设计。据说影片的灵感有部分来自侯世达先生的《集异璧》。这是本涉及甚广的奇书,试图综合各学科的知识来探讨意识的机制。 侯先生虽然也不能完全回答自己提出的这个问题,但他猜想意识的关键之处在于“我”这个概念的产生。而这个概念来自自我和外界的区分,来自人类和外界不断的 信息交流。于是对“我”的认知从出生时起就一层层叠进脑内,这种交流积累终其一生循环往复。书中曾把这个过程类比于函数对自身进行循环调用,那么影片中的 故事设计与计算机原理相似的情况倒也不太出乎人意料。

咦,那么有没有可能,导演在试图把计算机科学的知识植入到我们的潜意识里面?银幕前面的你,被他的Inception施中了吗?

========

注:已发于《开啦》电子杂志,此处为修改版。写作过程中参考了凍啡走甜的博文,Albert_JIAO和白鸟老师提供了非常好的修改建议,特此感谢。

0
为您推荐

67 Responses to “Inception:一场层层调用的函数大战”

  1. tiger lee说道:

    用函数来形容这部电影很有创意,站在程序员的角度来看,一层一层的梦境确实就像函数调用一般,但这个也只是个轮廓像而已,不是完全能和剧情吻合的。
    作者提到cobb的妻子在limbo呆久了而导致堆栈溢出,那么作为一个程序,它是不会正确的返回到刚开始函数调用的地方,也就是剧情中的现实。当然,如果我们把现实层比喻为在存储介质中的程序,那么兴许可以解释这一段。
    1. 进入第一层梦境表示程序被操作系统load进内存(我们假定这个程序只有一个main函数,不停地做梦只是对该函数的递归调用,这样描述起来会方便一些)
    2. 进入第二层梦境则是在main()中继续调用main(),往后依此类推。
    3. 当堆栈溢出后,操作系统会将此进程终止运行。这些程序结束了在内存中的旅程。(操作系统则表示自然规律或是剧中的条件设定)

    我的比喻只是对堆栈溢出的一种看法,不代表对全剧的比喻。总而言之,将梦境比喻成函数调用是我见过的影评中比较有意思的,符合程序员思维的。可惜我看完后却没有想到,不够敏感啊。

    • 黄某说道:

      文章好懂,回复也有深度!

    • anpopo说道:

      恩,完全和剧情吻合的话光计算机的比喻不够吧。这里的本意是借电影介绍一下相关知识,能引出这么大一沙发,我好满足啊

  2. cobblest说道:

    哇,婆婆上了老徐的杂志啦~恭喜恭喜~
    这篇真好看……

  3. Peter说道:

    递归调用么。其实梦中做梦这个问题在我上小学的时候就进行过深入的思考,最后的结果是。。。。。。。。睡着了

  4. jenny-young说道:

    看了一半就成地板了

  5. jorbin说道:

    直接看晕了。。。

  6. diesirae说道:

    时而做梦中梦,有时在梦中还能判断出正在做梦,并且试验在梦中掐自己是否会疼的人路过……

  7. yuki 琦说道:

    网上有文说inception还和欧式几何有关呢,现在一看,原来还和堆栈溢出有关。真是大有启发呀!!

  8. ssfighter说道:

    写的不错。哈哈

  9. 孤竹牧狼人说道:

    很有趣的文章~........
    小女孩很可爱.......演juno时候就很可爱.......
    仅次于小时候的赫敏......
    哈哈

    不过电影有很多bug~
    用函数调用的角度来看的话,比如
    为什么一层梦境中的汽车落水失重影响到了第二层梦境的漂浮状态~
    但是对第三第四层毫无作用......
    还有“Totem”......在电影本身的理论基础中做不到自洽.....是不可能存在的......

  10. ttt说道:

    在limbo死掉就是抛出了一个异常

    然后就回到顶层了

  11. gao说道:

    我看完电影的第一个想法就是递归调用,不过有一个问题不能解释,就是这不是单线程的,内外层是并行的。

    • 打破的管道说道:

      递归不一定非要是单线程的,可以在递归函数中起一个新的线程,这个新线程执行的函数正是调用函数自身

  12. 打破的管道说道:

    这个对比太精辟了

  13. 打破的管道说道:

    不过我没听说过栈溢出还能修改内存的,可能是我才疏学浅吧

    • anpopo说道:

      嗯,我的粗浅理解,栈溢出实际上就是内存发生了错误改动,不是么?管道兄多解释一下吧

      • 打破的管道说道:

        好像并非如你所讲。

        作为一个不甚合格的程序员,我在这里斗胆说一下我对栈溢出的理解吧。

        其实在一个程序员的日常工作中,“栈溢出”这三个字并不多见,更多见的是程序发生stack overflow错误(其实也就是栈溢出,只不过是英语写的啦)。

        操作系统分配给一个进程的最大栈内存一般都很小,大概几Mb吧,当进程用完了
        这点儿栈内存就会报stack overflow错误。安婆婆所说的函数调用层数过多也会引发这个错误。其实重现这个错误的方法很简单,即便是刚学一点C语言的学弟也可以,只要在main函数里直接调用main函数就行了,像这样:
        main(){ main();}
        程序会递归下去,直到栈内存耗尽,但耗尽之后也只是会报一个错误,告诉我们栈内存不够用了,而不会去做其他事情,例如覆盖原有的数据。

        所以,stack overflow这条信息可以理解为run out of stack memory。不知道这样的解释是否够清楚。

        至于安婆婆说的覆盖原来的栈内数据,可能是受“缓冲区溢出”这个概念的干扰。和栈溢出不同,缓冲区溢出并不需要用光栈的内存,它是一种常见的黑客攻击手段,具体原理就不费劲在这里解释了。

  14. gyro说道:

    还没看过此电影。。
    不过堆栈溢出一般的程序都是主程序被抛出操作系统,因为操作系统要时刻保证自己的可用资源,win9x这种伟大的系统就不算了。

    看剧情,似乎是某些函数在递归的过程中,内存不足,丢掉了自己的返回地址。
    然后其他函数又再次写入了这个函数地址,不过这哥们似乎忘了重置堆栈列表,结果就是没正确返回地址,却又调用了一个函数,而这个函数,系统把他作为了主函数执行,主函数被覆盖的以后,所有函数的返回地址全部崩溃作废,于是。。

    只有指针溢出才能修改内存。。

    • anpopo说道:

      那么指针溢出和堆栈溢出有什么不同呢?求科普~~

      • gyro说道:

        指针,是一个标志,标识一个内存地址,被一个变量使用的开始值,结束值由程序来判断该变量有多长。但是如果判断出错,结束值就会远远大于正常值,那么就会写入到其他变量,甚至干脆写到系统内核使用的内存去,这就是溢出破解。
        堆栈,指的是程序内部对函数调用的一个列表一样的东西,这个表格有长度限制,也会有程序可以修改它。溢出,一般指长度超出限制。当然,也可以通过指针溢出的方法来修改堆栈的返回地址之类的。实际上,黑客们最喜欢的就是这个,找到一个没有检查长度限制的变量,输入变量,尝试溢出,溢出没问题,构建代码,继续溢出,一直溢出到系统内核使用的内存空间,然后,用户程序执行完了程序,需要把资源交还给系统内核了,按照堆栈里面的返回地址去找系统内核所在的内存空间,但是这个时候,这个地址对应的内存空间里面已经是黑客做的代码了,于是,系统被控。。。。
        这才是黑客做的事情,不是做到电脑前面,噼里啪啦一堆键盘,就看满屏幕字符乱飞,一个滚动条滚动,得,进去了,严重汗一个。。。

        • hubert说道:

          说得很对,但是没有安婆婆写的容易懂。安婆婆写的虽然易懂,但与事实略有不同。

        • ghost说道:

          >>一直溢出到系统内核使用的内存空间

          不用溢出到系統內核空間,也不可能溢出到系統內核空間,因為系統內核總是佔用最前面的內存。

          緩衝區溢出大概可以這麼理解:函數A調用下層函數B的時候,會在堆棧裡留個路牌:函數B,當你執行完請按此路牌返回。

          例如:
          fun A()
          1 start
          2 do sth
          3 call fun B
          4 next
          5 exit

          其中路牌內容就應該是A:4(注意不是A:3,不然會導致無限調用函數B的)

          這樣,當函數B執行結束的時候,就會根據函數A留下的路牌返回到函數A的第4行。

          在計算機裡,其實是沒有函數A、代碼行數4這樣的東西的,而是會轉化成一個比較長的數字,也就是內容地址。

          比如A:3,實際地址可能就是 AABB:11223344
          而A:4,實際地址可能是 AABB:11223350 (這說明call fun b這個指令佔用了12字節,當然,這個數字不是真的)

          因為同一線程的所有函數公用一個棧,而棧最頂上會放上當前執行的函數的局部變量(這個例子裡就是B的局部變量);同時,按照約定俗成的規定,棧空間是反向增長的(也就是說,A的局部變量以及它設的路牌會在B的局部變量之後而不是之前),所以,如果B的局部變量寫溢出,那麼就可能覆蓋掉A設置的路牌,導致B執行結束,返回到一個未知的地點繼續執行——注意這個路牌既然可以覆蓋,那麼自然可以被精心設計的攻擊者寫上某個他們喜歡的東西,使得B返回後,去執行黑客們喜歡的代碼。

          黑客們就可以利用這點,讓一個本來循規蹈矩的好孩子“出軌”,執行一些這個程序本來不可能去做的事——嗯,比如發條消息,讓你的QQ刪掉你的C:\windows:如果QQ有緩衝區漏洞的話。

          PS:說句題外話:我們說linux/unix安全,就是因為Linux/unix下的所有程序都有默認精當的權限設置,而不像Windows一樣全都是管理員權限;這樣萬一真有緩衝區漏洞,在Windows下黑客就可以利用QQ刪掉windows目錄,而linux/unix下的類似程序,就連列個目錄都可能被系統禁止。

          其實Windows一樣可以控制程序權限,一樣可以做到很安全。但由於根深蒂固的習慣,數目龐大的程序仍然擁有著不合適的高權限;而且同樣由於習慣,Windows程序員拿訪問敏感區域當家常便飯,以至於很多程序改低權限就不能運行。

          當然,win vista和win7因為引入了UAC,情況得到了的緩解。

          • anpopo说道:

            你们太牛了!

            不过最近我们实验室有一台服务器中了Linux.RST.B 木马,颠覆了大家“Linux下无病毒”的信念……

  15. 沿见说道:

    我觉得小李最后陷入死循环了...

  16. zhuoqun说道:

    迭代的是人,递归的是神。

    • anpopo说道:

      据说巴赫在他自己的乐谱里搞递归,把自己名字代表的音阶写进乐章,结果他就死了……

  17. 艾梨说道:

    帅,升入浅出!

  18. 说道:

    除了跟数学有关原来跟计算机知识也有关。导演强的。解读的也强

  19. MarsV说道:

    插个栈的示意图可能能更好地帮助非计算机专业的同学理解

  20. 幸福似小猪说道:

    向你致敬~ ^_^

  21. 0.618说道:

    婆婆这文比电影NB啊!

  22. LaRoux说道:

    恩!不过我觉得就电影来说,拍的基调太亮了。没有想象中的那么好

  23. sqybi说道:

    哈,果断栈溢出~

  24. anpopo说道:

    关于电影的比喻当然远远不止计算机,只是其他的都很难写,俺捡了个软柿子捏罢了,呵呵。大家看了开心俺也很开心,欢迎各种借题发挥,借景生情,借风行船,借尸还魂。。。

  25. egggj说道:

    就我对影片结尾的理解,男主角才是一直在梦境里,他老婆从跳楼的那一刻就回到现实(或上一层梦境)了。

  26. rainshayne说道:

    引发大范围或质疑、或解析的思考和讨论,这是Inception 最NB、最成功的地方

  27. capsray说道:

    很不情愿的说,我真的有点懵了

  28. yaocc说道:

    最开始我也想到了 函数的嵌套调用

  29. 说道:

    婆婆出手,非同凡响,大爱!

  30. Filter说道:

    xd,我怎么觉得应该是“迭代”呀?这也符合后进先出的原则……而且环境一致、参数不同……
    当然“堆栈”的描述也差不多,不过能进“栈”的不一定是同样的函数哟……
    ========================================
    10多年前上学的基础课现在都快忘光了……唉

  31. nneerr123说道:

    《生活在别处》里的诗人

  32. Dickens说道:

    小弟三年前看了《集异璧》,在看电影时也是连连叫爽。除了堆栈概念之外,书中关于现实与虚拟的讨论也是在电影中引人思考的一个元素。

  33. sheldon说道:

    豆瓣上咋没搜到这本书?

  34. 吴小毛说道:

    哈 用计算机的角度来解释比用数学的角度解释来得直观

  35. 爆了说道:

    递归、递推、迭代分别是什么意思,有什么区别?

  36. ether说道:

    缓冲区溢出病毒。。。。

  37. lookyn NT说道:

    还没看电影,不过感觉某些概念跟攻壳机动队很多类似:ghost,意识潜入,攻性防壁,防壁迷宫...

  38. wencan说道:

    当下层的梦在执行时
    上层的梦依然在运行

    当上层终止时,不管下层终止与否,都会自动结束

    这更像是多线程,而不是函数

  39. 嘻嘻哈哈说道:

    在我看来层层梦境不过是一个数学式中的大括号中括号小括号罢了

  40. 高言说道:

    好 !!

  41. 小罗说道:

    有意思!

  42. yang_bigarm说道:

    我也认为这个电影受到《集异壁》的影响

    电影中看到cobb训练那个女孩的时候,带她走了一段四边形的楼梯,那个楼梯就跟埃舍尔的画里面那个楼梯是一样的,当时我就想到《集异壁》里面也有这个场景。

  43. digiter说道:

    插图外面再套一圈GC吧O(∩_∩)O~

  44. william H.Wei说道:

    https://github.com/karthick18/inception/blob/master/inception.c
    这个网站就写了个程序来描述整部电影情节的,用的是标准C语言吧,程序很容易读的,多年不写C,好几年不搞IT的我都能看明白。

  45. Albertcool说道:

    喜欢用程序的函数思想描述这个~恩 不错
    哎 好久没写程序了~

  46. 这样看来,什么都可以用这个函数调用来解释,来说明了。

  47. 比尔盖子说道:

    这个我梦境认为不是函数与堆栈,是虚拟机,见我的博文:

    《盗梦空间》算是2011年的神奇电影了,很多人看完都说这是给程序员看的,一层层的梦境就像函数的调用。不过我倒不同意梦境就相当于函数的调用,因为当函数嵌套在一起时,里面的会比外面的先运行,而梦境则是同时运行的。比尔盖子认为梦境更像是虚拟机,下面比尔盖子就和大家一起分析一下,并用虚拟机的思想来解决《盗梦空间》中的一些问题。

    问题1:为什么人被造梦时难以发现自己在做梦?

    这其实就是一个关于虚拟机的基本知识题。虚拟机,可以虚拟出一切电脑硬件(虽然有些是和真实电脑共享的):显卡、声卡、内存、硬盘、网卡、主板、USB控制器……所以说在虚拟机里的程序会像在真实电脑里的程序一样正常运行。

    问题2:为什么有些人发现了自己是在做梦?“图腾”是怎么回事?

    当人发现梦中有些情况与现实不符时,就会醒来。而在虚拟机里,这也是成立的,如果有程序不想再虚拟机里运行,它会检查CPU主频,如果发现虽然主频是2Ghz(假设),但是实际效率远远离这个数差远了,就会认为自己身处虚拟机。

    另外,在《盗梦空间》中,造梦师都会有一个自己的“图腾”,因为梦中空间的有限性,“图腾”在梦中就会失去某种随机性,这样造梦师就可以知道自己在梦中了。在虚拟机中也是一样,VMtools(虚拟机使用辅助工具)就是“图腾”,它也是虚拟机的唯一标识。

    问题3:多层梦境是怎么回事?

    在《盗梦空间》中,有一些高级的造梦师可以制造多层梦境,这在虚拟机中也是完全可以的。既然虚拟机和电脑别无两样,在理论上我们完全可以在虚拟机里运行一台虚拟机,也可以在虚拟机的虚拟机里运行一台虚拟机,只要硬件性能合格。但是,在虚拟机中,虚拟机软件会检测到它身处虚拟机,于是就会禁止你安装和运行虚拟机软件(虚拟机用的方法是“问题2”的第一种方法)。但这是人为故意导致的结果,其实完全可以这么做。而且有人已经做到了:在VMware虚拟机中运行虚拟机。

    问题4:为什么在梦中的时间很慢?

    这可以用虚拟机的性能下降来解释,当你运行一个虚拟机时,它的性能会比物理主机的性能出现一定程度的下降,这样虚拟机的运算能力也会下降。往往需要花比物理主机数倍的时间来完成一件事,也就是运行速度变慢了。让我们回到梦中也是一样,梦中的运行速度越慢,里面的时间就会越慢。

    问题5:用音乐来同步各个梦境时间可行吗?

    因为虚拟机是共用一个声卡的,所以这个是可行的(当然,你说你在一个虚拟机里听歌,其它虚拟机里不会有音量显示,这说明你把输入输出弄混了。在《盗梦空间》里,现实世界的人会给梦境里的人输入音乐,而不是让梦境里的人输出音乐)。我们可以来验证这个事实,让我们用麦克风说话,这时无论是物理主机、虚拟机、虚拟机中的虚拟机将都会有输入音量的指示,使用声卡的“输入”插口也能达到一样的效果。

    这么一说,看来我们的虚拟机理论相当完美啊!

  48. Diablo 3说道:

    Hey! Do you use Twitter? I'd like to follow you if that would be ok. I'm definitely enjoying your blog and look forward to new posts.

Leave a Reply