久久99精品久久久久久琪琪,久久人人爽人人爽人人片亞洲,熟妇人妻无码中文字幕,亚洲精品无码久久久久久久

C語言缺陷與陷阱

時(shí)間:2019-05-14 02:12:16下載本文作者:會員上傳
簡介:寫寫幫文庫小編為你整理了多篇相關(guān)的《C語言缺陷與陷阱》,但愿對你工作學(xué)習(xí)有幫助,當(dāng)然你在寫寫幫文庫還可以找到更多《C語言缺陷與陷阱》。

第一篇:C語言缺陷與陷阱

C語言陷阱和缺陷

[譯序]

那些自認(rèn)為已經(jīng)“學(xué)完”C語言的人,請你們仔細(xì)讀閱讀這篇文章吧。路還長,很多東西要學(xué)。我也是??

[概述]

C語言像一把雕刻刀,鋒利,并且在技師手中非常有用。和任何鋒利的工具一樣,C會傷到那些不能掌握它的人。本文介紹C語言傷害粗心的人的方法,以及如何避免傷害。

[內(nèi)容]

0 簡介 1 詞法缺陷 1.1 = 不是 == 1.2 & 和 | 不是 && 和 || 1.3 多字符記號 1.4 例外

1.5 字符串和字符 2 句法缺陷 2.1 理解聲明

2.2 運(yùn)算符并不總是具有你所想象的優(yōu)先級 2.3 看看這些分號!2.4 switch語句 2.5 函數(shù)調(diào)用

2.6 懸掛else問題 3 鏈接

3.1 你必須自己檢查外部類型 4 語義缺陷

4.1 表達(dá)式求值順序 4.2 &&、||和!運(yùn)算符 4.3 下標(biāo)從零開始

4.4 C并不總是轉(zhuǎn)換實(shí)參 4.5 指針不是數(shù)組 4.6 避免提喻法

4.7 空指針不是空字符串 4.8 整數(shù)溢出 4.9 移位運(yùn)算符 5 庫函數(shù)

5.1 getc()返回整數(shù) 5.2 緩沖輸出和內(nèi)存分配 6 預(yù)處理器 6.1 宏不是函數(shù) 6.2 宏不是類型定義 7 可移植性缺陷

7.1 一個(gè)名字中都有什么? 7.2 一個(gè)整數(shù)有多大?

7.3 字符是帶符號的還是無符號的? 7.4 右移位是帶符號的還是無符號的? 7.5 除法如何舍入? 7.6 一個(gè)隨機(jī)數(shù)有多大? 7.7 大小寫轉(zhuǎn)換

7.8 先釋放,再重新分配 7.9 可移植性問題的一個(gè)實(shí)例 8 這里是空閑空間 參考 腳注

0 簡介

C語言及其典型實(shí)現(xiàn)被設(shè)計(jì)為能被專家們?nèi)菀椎厥褂谩_@門語言簡潔并附有表達(dá)力。但有一些限制可以保護(hù)那些浮躁的人。一個(gè)浮躁的人可以從這些條款中獲得一些幫助。

在本文中,我們將會看一看這些未可知的益處。這是由于它的未可知,我們無法為其進(jìn)行完全的分類。不過,我們?nèi)匀煌ㄟ^研究為了一個(gè)C程序的運(yùn)行所需要做的事來做到這些。我們假設(shè)讀者對C語言至少有個(gè)粗淺的了解。

第一部分研究了當(dāng)程序被劃分為記號時(shí)會發(fā)生的問題。第二部分繼續(xù)研究了當(dāng)程序的記號被編譯器組合為聲明、表達(dá)式和語句時(shí)會出現(xiàn)的問題。第三部分研究了由多個(gè)部分組成、分別編譯并綁定到一起的C程序。第四部分處理了概念上的誤解:當(dāng)一個(gè)程序具體執(zhí)行時(shí)會發(fā)生的事情。第五部分研究了我們的程序和它們所使用的常用庫之間的關(guān)系。在第六部分中,我們注意到了我們所寫的程序也不并不是我們所運(yùn)行的程序;預(yù)處理器將首先運(yùn)行。最后,第七部分討論了可移植性問題:一個(gè)能在一個(gè)實(shí)現(xiàn)中運(yùn)行的程序無法在另一個(gè)實(shí)現(xiàn)中運(yùn)行的原因。詞法缺陷

編譯器的第一個(gè)部分常被稱為詞法分析器(lexical analyzer)。詞法分析器檢查組成程序的字符序列,并將它們劃分為記號(token)一個(gè)記號是一個(gè)有一個(gè)或多個(gè)字符的序列,它在語言被編譯時(shí)具有一個(gè)(相關(guān)地)統(tǒng)一的意義。在C中,例如,記號->的意義和組成它的每個(gè)獨(dú)立的字符具有明顯的區(qū)別,而且其意義獨(dú)立于->出現(xiàn)的上下文環(huán)境。

另外一個(gè)例子,考慮下面的語句:

if(x > big)big = x;

該語句中的每一個(gè)分離的字符都被劃分為一個(gè)記號,除了關(guān)鍵字if和標(biāo)識符big的兩個(gè)實(shí)例。

事實(shí)上,C程序被兩次劃分為記號。首先是預(yù)處理器讀取程序。它必須對程序進(jìn)行記號劃分以發(fā)現(xiàn)標(biāo)識宏的標(biāo)識符。它必須通過對每個(gè)宏進(jìn)行求值來替換宏調(diào)用。最后,經(jīng)過宏替換的程序又被匯集成字符流送給編譯器。編譯器再第二次將這個(gè)流劃分為記號。

在這一節(jié)中,我們將探索對記號的意義的普遍的誤解以及記號和組成它們的字符之間的關(guān)系。稍后我們將談到預(yù)處理器。

1.1 = 不是 == 從Algol派生出來的語言,如Pascal和Ada,用:=表示賦值而用=表示比較。而C語言則是用=表示賦值而用==表示比較。這是因?yàn)橘x值的頻率要高于比較,因此為其分配更短的符號。

此外,C還將賦值視為一個(gè)運(yùn)算符,因此可以很容易地寫出多重賦值(如a = b = c),并且可以將賦值嵌入到一個(gè)大的表達(dá)式中。

這種便捷導(dǎo)致了一個(gè)潛在的問題:可能將需要比較的地方寫成賦值。因此,下面的語句好像看起來是要檢查x是否等于y:

if(x = y)foo();

而實(shí)際上是將x設(shè)置為y的值并檢查結(jié)果是否非零。在考慮下面的一個(gè)希望跳過空格、制表符和換行符的循環(huán):

while(c == ' ' || c = 't' || c == 'n')c = getc(f);

在與't'進(jìn)行比較的地方程序員錯(cuò)誤地使用=代替了==。這個(gè)“比較”實(shí)際上是將't'賦給c,然后判斷c的(新的)值是否為零。因?yàn)?t'不為零,這個(gè)“比較”將一直為真,因此這個(gè)循環(huán)會吃盡整個(gè)文件。這之后會發(fā)生什么取決于特定的實(shí)現(xiàn)是否允許一個(gè)程序讀取超過文件尾部的部分。如果允許,這個(gè)循環(huán)會一直運(yùn)行。

一些C編譯器會對形如e1 = e2的條件給出一個(gè)警告以提醒用戶。當(dāng)你趨勢需要先對一個(gè)變量進(jìn)行賦值之后再檢查變量是否非零時(shí),為了在這種編譯器中避免警告信息,應(yīng)考慮顯式給出比較符。換句話說,將:

if(x = y)foo();

改寫為:

if((x = y)!= 0)foo();

這樣可以清晰地表示你的意圖。

1.2 & 和 | 不是 && 和 || 容易將==錯(cuò)寫為=是因?yàn)楹芏嗥渌Z言使用=表示比較運(yùn)算。其他容易寫錯(cuò)的運(yùn)算符還有&和&&,或|和||,這主要是因?yàn)镃語言中的&和|運(yùn)算符于其他語言中具有類似功能的運(yùn)算符大為不同。我們將在第4節(jié)中貼近地觀察這些運(yùn)算符。

1.3 多字符記號

一些C記號,如/、*和=只有一個(gè)字符。而其他一些C記號,如/*和==,以及標(biāo)識符,具有多個(gè)字符。當(dāng)C編譯器遇到緊連在一起的/和*時(shí),它必須能夠決定是將這兩個(gè)字符識別為兩個(gè)分離的記號還是一個(gè)單獨(dú)的記號。C語言參考手冊說明了如何決定:“如果輸入流到一個(gè)給定的字符串為止已經(jīng)被識別為記號,則應(yīng)該包含下一個(gè)字符以組成能夠構(gòu)成記號的最長的字符串”。因此,如果/是一個(gè)記號的第一個(gè)字符,并且/后面緊隨了一個(gè)*,則這兩個(gè)字符構(gòu)成了注釋的開始,不管其他上下文環(huán)境。

下面的語句看起來像是將y的值設(shè)置為x的值除以p所指向的值:

y = x/*p /* p 指向除數(shù) */;

實(shí)際上,/*開始了一個(gè)注釋,因此編譯器簡單地吞噬程序文本,直到*/的出現(xiàn)。換句話說,這條語句僅僅把y的值設(shè)置為x的值,而根本沒有看到p。將這條語句重寫為:

y = x / *p /* p 指向除數(shù) */;

或者干脆是

y = x /(*p)/* p指向除數(shù) */;

它就可以做注釋所暗示的除法了。

這種模棱兩可的寫法在其他環(huán)境中就會引起麻煩。例如,老版本的C使用=+表示現(xiàn)在版本中的+=。這樣的編譯器會將

a=-1;

視為

a =-1;或

a = a> a

是不合法的。它和

p-> a

不是同義詞。

另一方面,有些老式編譯器還是將=+視為一個(gè)單獨(dú)的記號并且和+=是同義詞。

1.5 字符串和字符

單引號和雙引號在C中的意義完全不同,在一些混亂的上下文中它們會導(dǎo)致奇怪的結(jié)果而不是錯(cuò)誤消息。

包圍在單引號中的一個(gè)字符只是書寫整數(shù)的另一種方法。這個(gè)整數(shù)是給定的字符在實(shí)現(xiàn)的對照序列中的一個(gè)對應(yīng)的值。因此,在一個(gè)ASCII實(shí)現(xiàn)中,'a'和0141或97表示完全相同的東西。而一個(gè)包圍在雙引號中的字符串,只是書寫一個(gè)有雙引號之間的字符和一個(gè)附加的二進(jìn)制值為零的字符所初始化的一個(gè)無名數(shù)組的指針的一種簡短方法。

線面的兩個(gè)程序片斷是等價(jià)的:

printf(“Hello worldn”);

char hello[] = { 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', 'n', 0 };printf(hello);

使用一個(gè)指針來代替一個(gè)整數(shù)通常會得到一個(gè)警告消息(反之亦然),使用雙引號來代替單引號也會得到一個(gè)警告消息(反之亦然)。但對于不檢查參數(shù)類型的編譯器卻除外。因此,用

printf('n');

來代替

printf(“n”);

通常會在運(yùn)行時(shí)得到奇怪的結(jié)果。

由于一個(gè)整數(shù)通常足夠大,以至于能夠放下多個(gè)字符,一些C編譯器允許在一個(gè)字符常量中存放多個(gè)字符。這意味著用'yes'代替“yes”將不會被發(fā)現(xiàn)。后者意味著“分別包含y、e、s和一個(gè)空字符的四個(gè)連續(xù)存貯器區(qū)域中的第一個(gè)的地址”,而前者意味著“在一些實(shí)現(xiàn)定義的樣式中表示由字符y、e、s聯(lián)合構(gòu)成的一個(gè)整數(shù)”。這兩者之間的任何一致性都純屬巧合。句法缺陷

要理解C語言程序,僅了解構(gòu)成它的記號是不夠的。還要理解這些記號是如何構(gòu)成聲明、表達(dá)式、語句和程序的。盡管這些構(gòu)成通常都是定義良好的,但這些定義有時(shí)候是有悖于直覺的或混亂的。

在這一節(jié)中,我們將著眼于一些不明顯句法構(gòu)造。

2.1 理解聲明

我曾經(jīng)和一些人聊過天,他們那時(shí)在書寫在一個(gè)小型的微處理器上單機(jī)運(yùn)行的C程序。當(dāng)這臺機(jī)器的開關(guān)打開的時(shí)候,硬件會調(diào)用地址為0處的子程序。

為了模仿電源打開的情形,我們要設(shè)計(jì)一條C語句來顯式地調(diào)用這個(gè)子程序。經(jīng)過一些思考,我們寫出了下面的語句:

(*(void(*)())0)();

這樣的表達(dá)式會令C程序員心驚膽戰(zhàn)。但是,并不需要這樣,因?yàn)樗麄兛梢栽谝粋€(gè)簡單的規(guī)則的幫助下很容易地構(gòu)造它:以你使用的方式聲明它。

每個(gè)C變量聲明都具有兩個(gè)部分:一個(gè)類型和一組具有特定格式的期望用來對該類型求值的表達(dá)式。最簡單的表達(dá)式就是一個(gè)變量:

float f, g;

說明表達(dá)式f和g——在求值的時(shí)候——具有類型float。由于待求值的時(shí)表達(dá)式,因此可以自由地使用圓括號: float((f));

者表示((f))求值為float并且因此,通過推斷,f也是一個(gè)float。

同樣的邏輯用在函數(shù)和指針類型。例如:

float ff();

表示表達(dá)式ff()是一個(gè)float,因此ff是一個(gè)返回一個(gè)float的函數(shù)。類似地,float *pf;

表示*pf是一個(gè)float并且因此pf是一個(gè)指向一個(gè)float的指針。

這些形式的組合聲明對表達(dá)式是一樣的。因此,float *g(),(*h)();

表示*g()和(*h)()都是float表達(dá)式。由于()比*綁定得更緊密,*g()和*(g())表示同樣的東西:g是一個(gè)返回指float指針的函數(shù),而h是一個(gè)指向返回float的函數(shù)的指針。

當(dāng)我們知道如何聲明一個(gè)給定類型的變量以后,就能夠很容易地寫出一個(gè)類型的模型(cast):只要?jiǎng)h除變量名和分號并將所有的東西包圍在一對圓括號中即可。因此,由于

float *g();

聲明g是一個(gè)返回float指針的函數(shù),所以(float *())就是它的模型。

有了這些知識的武裝,我們現(xiàn)在可以準(zhǔn)備解決(*(void(*)())0)()了。我們可以將它分為兩個(gè)部分進(jìn)行分析。首先,假設(shè)我們有一個(gè)變量fp,它包含了一個(gè)函數(shù)指針,并且我們希望調(diào)用fp所指向的函數(shù)。可以這樣寫:

(*fp)();

如果fp是一個(gè)指向函數(shù)的指針,則*fp就是函數(shù)本身,因此(*fp)()是調(diào)用它的一種方法。(*fp)中的括號是必須的,否則這個(gè)表達(dá)式將會被分析為*(fp())。我們現(xiàn)在要找一個(gè)適當(dāng)?shù)谋磉_(dá)式來替換fp。

這個(gè)問題就是我們的第二步分析。如果C可以讀入并理解類型,我們可以寫:

(*0)();

但這樣并不行,因?yàn)?運(yùn)算符要求必須有一個(gè)指針作為他的操作數(shù)。另外,這個(gè)操作數(shù)必須是一個(gè)指向函數(shù)的指針,以保證*的結(jié)果可以被調(diào)用。因此,我們需要將0轉(zhuǎn)換為一個(gè)可以描述“指向一個(gè)返回void的函數(shù)的指針”的類型。如果fp是一個(gè)指向返回void的函數(shù)的指針,則(*fp)()是一個(gè)void值,并且它的聲明將會是這樣的:

void(*fp)();

因此,我們需要寫:

void(*fp)();(*fp)();

來聲明一個(gè)啞變量。一旦我們知道了如何聲明該變量,我們也就知道了如何將一個(gè)常數(shù)轉(zhuǎn)換為該類型:只要從變量的聲明中去掉名字即可。因此,我們像下面這樣將0轉(zhuǎn)換為一個(gè)“指向返回void的函數(shù)的指針”:

(void(*)())0

接下來,我們用(void(*)())0來替換fp:

(*(void(*)())0)();

結(jié)尾處的分號用于將這個(gè)表達(dá)式轉(zhuǎn)換為一個(gè)語句。

在這里,我們就解決了這個(gè)問題時(shí)沒有使用typedef聲明。通過使用它,我們可以更清晰地解決這個(gè)問題:

typedef void(*funcptr)();(*(funcptr)0)();

2.2 運(yùn)算符并不總是具有你所想象的優(yōu)先級

假設(shè)有一個(gè)聲明了的常量FLAG是一個(gè)整數(shù),其二進(jìn)制表示中的某一位被置位(換句話說,它是2的某次冪),并且你希望測試一個(gè)整型變量flags該位是否被置位。通常的寫法是:

if(flags & FLAG)...其意義對于很多C程序員都是很明確的:if語句測試?yán)ㄌ栔械谋磉_(dá)式求值的結(jié)果是否為0。出于清晰的目的我們可以將它寫得更明確:

if(flags & FLAG!= 0)...這個(gè)語句現(xiàn)在更容易理解了。但它仍然是錯(cuò)的,因?yàn)?=比&綁定得更緊密,因此它被分析為:

if(flags &(FLAG!= 0))...這(偶爾)是可以的,如FLAG是1或0(!)的時(shí)候,但對于其他2的冪是不行的[2]。

假設(shè)你有兩個(gè)整型變量,h和l,它們的值在0和15(含0和15)之間,并且你希望將r設(shè)置為8位值,其低位為l,高位為h。一種自然的寫法是:

r = h << 4 + 1;

不幸的是,這是錯(cuò)誤的。加法比移位綁定得更緊密,因此這個(gè)例子等價(jià)于:

r = h <<(4 + l);

正確的方法有兩種:

r =(h << 4)+ l;

r = h << 4 | l;

避免這種問題的一個(gè)方法是將所有的東西都用括號括起來,但表達(dá)式中的括號過度就會難以理解,因此最好還是是記住C中的優(yōu)先級。

不幸的是,這有15個(gè),太困難了。然而,通過將它們分組可以變得容易。

綁定得最緊密的運(yùn)算符并不是真正的運(yùn)算符:下標(biāo)、函數(shù)調(diào)用和結(jié)構(gòu)選擇。這些都與左邊相關(guān)聯(lián)。

接下來是一元運(yùn)算符。它們具有真正的運(yùn)算符中的最高優(yōu)先級。由于函數(shù)調(diào)用比一元運(yùn)算符綁定得更緊密,你必須寫(*p)()來調(diào)用p指向的函數(shù);*p()表示p是一個(gè)返回一個(gè)指針的函數(shù)。轉(zhuǎn)換是一元運(yùn)算符,并且和其他一元運(yùn)算符具有相同的優(yōu)先級。一元運(yùn)算符是右結(jié)合的,因此*p++表示*(p++),而不是(*p)++。

在接下來是真正的二元運(yùn)算符。其中數(shù)學(xué)運(yùn)算符具有最高的優(yōu)先級,然后是移位運(yùn)算符、關(guān)系運(yùn)算符、邏輯運(yùn)算符、賦值運(yùn)算符,最后是條件運(yùn)算符。需要記住的兩個(gè)重要的東西是:

所有的邏輯運(yùn)算符具有比所有關(guān)系運(yùn)算符都低的優(yōu)先級。

一位運(yùn)算符比關(guān)系運(yùn)算符綁定得更緊密,但又不如數(shù)學(xué)運(yùn)算符。

在這些運(yùn)算符類別中,有一些奇怪的地方。乘法、除法和求余具有相同的優(yōu)先級,加法和減法具有相同的優(yōu)先級,以及移位運(yùn)算符具有相同的優(yōu)先級。

還有就是六個(gè)關(guān)系運(yùn)算符并不具有相同的優(yōu)先級:==和!=的優(yōu)先級比其他關(guān)系運(yùn)算符要低。這就允許我們判斷a和b是否具有與c和d相同的順序,例如:

a < b == c < d

在邏輯運(yùn)算符中,沒有任何兩個(gè)具有相同的優(yōu)先級。按位運(yùn)算符比所有順序運(yùn)算符綁定得都緊密,每種與運(yùn)算符都比相應(yīng)的或運(yùn)算符綁定得更緊密,并且按位異或(^)運(yùn)算符介于按位與和按位或之間。

三元運(yùn)算符的優(yōu)先級比我們提到過的所有運(yùn)算符的優(yōu)先級都低。這可以保證選擇表達(dá)式中包含的關(guān)系運(yùn)算符的邏輯組合特性,如:

z = a < b && b < c ? d : e

這個(gè)例子還說明了賦值運(yùn)算符具有比條件運(yùn)算符更低的優(yōu)先級是有意義的。另外,所有的復(fù)合賦值運(yùn)算符具有相同的優(yōu)先級并且是自右至左結(jié)合的,因此

a = b = c 和

b = c;a = b;

是等價(jià)的。

具有最低優(yōu)先級的是逗號運(yùn)算符。這很容易理解,因?yàn)槎禾柾ǔT谛枰磉_(dá)式而不是語句的時(shí)候用來替代分號。

賦值是另一種運(yùn)算符,通常具有混合的優(yōu)先級。例如,考慮下面這個(gè)用于復(fù)制文件的循環(huán):

while(c = getc(in)!= EOF)putc(c, out);

這個(gè)while循環(huán)中的表達(dá)式看起來像是c被賦以getc(in)的值,接下來判斷是否等于EOF以結(jié)束循環(huán)。不幸的是,賦值的優(yōu)先級比任何比較操作都低,因此c的值將會是getc(in)和EOF比較的結(jié)果,并且會被拋棄。因此,“復(fù)制”得到的文件將是一個(gè)由值為1的字節(jié)流組成的文件。

上面這個(gè)例子正確的寫法并不難:

while((c = getc(in))!= EOF)putc(c, out);

然而,這種錯(cuò)誤在很多復(fù)雜的表達(dá)式中卻很難被發(fā)現(xiàn)。例如,隨UNIX系統(tǒng)一同發(fā)布的lint程序通常帶有下面的錯(cuò)誤行:

if(((t = BTYPE(pt1->aty)== STRTY)|| t == UNIONTY){

這條語句希望給t賦一個(gè)值,然后看t是否與STRTY或UNIONTY相等。而實(shí)際的效果卻大不相同[3]。

C中的邏輯運(yùn)算符的優(yōu)先級具有歷史原因。B——C的前輩——具有和C中的&和|運(yùn)算符對應(yīng)的邏輯運(yùn)算符。盡管它們的定義是按位的,但編譯器在條件判斷上下文中將它們視為和&&和||一樣。當(dāng)在C中將它們分開后,優(yōu)先級的改變是很危險(xiǎn)的[4]。

2.3 看看這些分號!

C中的一個(gè)多余的分號通常會帶來一點(diǎn)點(diǎn)不同:或者是一個(gè)空語句,無任何效果;或者編譯器可能提出一個(gè)診斷消息,可以方便除去掉它。一個(gè)重要的區(qū)別是在必須跟有一個(gè)語句的if和while語句中。考慮下面的例子:

if(x > big);big = x;這不會發(fā)生編譯錯(cuò)誤,但這段程序的意義與: if(x > big)big = x;就大不相同了。第一個(gè)程序段等價(jià)于: if(x > big){ } big = x;也就是等價(jià)于: big = x;(除非x、i或big是帶有副作用的宏)。另一個(gè)因分號引起巨大不同的地方是函數(shù)定義前面的結(jié)構(gòu)聲明的末尾[譯注:這句話不太好聽,看例子就明白了]。考慮下面的程序片段: struct foo { int x;} f(){...} 在緊挨著f的第一個(gè)}后面丟失了一個(gè)分號。它的效果是聲明了一個(gè)函數(shù)f,返回值類型是struct foo,這個(gè)結(jié)構(gòu)成了函數(shù)聲明的一部分。如果這里出現(xiàn)了分號,則f將被定義為具有默認(rèn)的整型返回值[5]。2.4 switch語句 通常C中的switch語句中的case段可以進(jìn)入下一個(gè)。例如,考慮下面的C和Pascal程序片斷: switch(color){ case 1: printf(“red”);break;case 2: printf(“yellow”);break;case 3: printf(“blue”);break;} case color of 1: write('red');2: write('yellow');3: write('blue');end 這兩個(gè)程序片斷都作相同的事情:根據(jù)變量color的值是1、2還是3打印red、yellow或blue(沒有新行符)。這兩個(gè)程序片斷非常相似,只有一點(diǎn)不同:Pascal程序中沒有C中相應(yīng)的break語句。C中的case標(biāo)簽是真正的標(biāo)簽:控制流程可以無限制地進(jìn)入到一個(gè)case標(biāo)簽中。看看另一種形式,假設(shè)C程序段看起來更像Pascal: switch(color){ case 1: printf(“red”);case 2: printf(“yellow”);case 3: printf(“blue”);} 并且假設(shè)color的值是2。則該程序?qū)⒋蛴ellowblue,因?yàn)榭刂谱匀坏剞D(zhuǎn)入到下一個(gè)printf()的調(diào)用。這既是C語言switch語句的優(yōu)點(diǎn)又是它的弱點(diǎn)。說它是弱點(diǎn),是因?yàn)楹苋菀淄浺粋€(gè)break語句,從而導(dǎo)致程序出現(xiàn)隱晦的異常行為。說它是優(yōu)點(diǎn),是因?yàn)橥ㄟ^故意去掉break語句,可以很容易實(shí)現(xiàn)其他方法難以實(shí)現(xiàn)的控制結(jié)構(gòu)。尤其是在一個(gè)大型的switch語句中,我們經(jīng)常發(fā)現(xiàn)對一個(gè)case的處理可以簡化其他一些特殊的處理。例如,設(shè)想有一個(gè)程序是一臺假想的機(jī)器的翻譯器。這樣的一個(gè)程序可能包含一個(gè)switch語句來處理各種操作碼。在這樣一臺機(jī)器上,通常減法在對其第二個(gè)運(yùn)算數(shù)進(jìn)行變號后就變成和加法一樣了。因此,最好可以寫出這樣的語句: case SUBTRACT: opnd2 =-opnd2;/* no break;*/ case ADD:...另外一個(gè)例子,考慮編譯器通過跳過空白字符來查找一個(gè)記號。這里,我們將空格、制表符和新行符視為是相同的,除了新行符還要引起行計(jì)數(shù)器的增長外: case 'n': linecount++;/* no break */ case 't': case ' ':...2.5 函數(shù)調(diào)用 和其他程序設(shè)計(jì)語言不同,C要求一個(gè)函數(shù)調(diào)用必須有一個(gè)參數(shù)列表,但可以沒有參數(shù)。因此,如果f是一個(gè)函數(shù),f();就是對該函數(shù)進(jìn)行調(diào)用的語句,而 f;什么也不做。它會作為函數(shù)地址被求值,但不會調(diào)用它[6]。2.6 懸掛else問題 在討論任何語法缺陷時(shí)我們都不會忘記提到這個(gè)問題。盡管這一問題不是C語言所獨(dú)有的,但它仍然傷害著那些有著多年經(jīng)驗(yàn)的C程序員。考慮下面的程序片斷: if(x == 0)if(y == 0)error();else { z = x + y;f(&z);} 寫這段程序的程序員的目的明顯是將情況分為兩種:x = 0和x!= 0。在第一種情況中,程序段什么都不做,除非y = 0時(shí)調(diào)用error()。第二種情況中,程序設(shè)置z = x + y并以z的地址作為參數(shù)調(diào)用f()。然而,這段程序的實(shí)際效果卻大為不同。其原因是一個(gè)else總是與其最近的if相關(guān)聯(lián)。如果我們希望這段程序能夠按照實(shí)際的情況運(yùn)行,應(yīng)該這樣寫: if(x == 0){ if(y == 0)error();else { z = x + y;f(&z);} } 換句話說,當(dāng)x!= 0發(fā)生時(shí)什么也不做。如果要達(dá)到第一個(gè)例子的效果,應(yīng)該寫: if(x == 0){ if(y ==0)error();} else { z = z + y;f(&z);} 3 鏈接 一個(gè)C程序可能有很多部分組成,它們被分別編譯,并由一個(gè)通常稱為鏈接器、鏈接編輯器或加載器的程序綁定到一起。由于編譯器一次通常只能看到一個(gè)文件,因此它無法檢測到需要程序的多個(gè)源文件的內(nèi)容才能發(fā)現(xiàn)的錯(cuò)誤。在這一節(jié)中,我們將看到一些這種類型的錯(cuò)誤。有一些C實(shí)現(xiàn),但不是所有的,帶有一個(gè)稱為lint的程序來捕獲這些錯(cuò)誤。如果具有一個(gè)這樣的程序,那么無論怎樣地強(qiáng)調(diào)它的重要性都不過分。3.1 你必須自己檢查外部類型 假設(shè)你有一個(gè)C程序,被劃分為兩個(gè)文件。其中一個(gè)包含如下聲明: int n;而令一個(gè)包含如下聲明: long n;這不是一個(gè)有效的C程序,因?yàn)橐恍┩獠棵Q在兩個(gè)文件中被聲明為不同的類型。然而,很多實(shí)現(xiàn)檢測不到這個(gè)錯(cuò)誤,因?yàn)榫幾g器在編譯其中一個(gè)文件時(shí)并不知道另一個(gè)文件的內(nèi)容。因此,檢查類型的工作只能由鏈接器(或一些工具程序如lint)來完成;如果操作系統(tǒng)的鏈接器不能識別數(shù)據(jù)類型,C編譯器也沒法過多地強(qiáng)制它。那么,這個(gè)程序運(yùn)行時(shí)實(shí)際會發(fā)生什么?這有很多可能性: 實(shí)現(xiàn)足夠聰明,能夠檢測到類型沖突。則我們會得到一個(gè)診斷消息,說明n在兩個(gè)文件中具有不同的類型。你所使用的實(shí)現(xiàn)將int和long視為相同的類型。典型的情況是機(jī)器可以自然地進(jìn)行32位運(yùn)算。在這種情況下你的程序或許能夠工作,好象你兩次都將變量聲明為long(或int)。但這種程序的工作純屬偶然。n的兩個(gè)實(shí)例需要不同的存儲,它們以某種方式共享存儲區(qū),即對其中一個(gè)的賦值對另一個(gè)也有效。這可能發(fā)生,例如,編譯器可以將int安排在long的低位。不論這是基于系統(tǒng)的還是基于機(jī)器的,這種程序的運(yùn)行同樣是偶然。n的兩個(gè)實(shí)例以另一種方式共享存儲區(qū),即對其中一個(gè)賦值的效果是對另一個(gè)賦以不同的值。在這種情況下,程序可能失敗。這種情況發(fā)生的里一個(gè)例子出奇地頻繁。程序的某一個(gè)文件包含下面的聲明: char filename[] = “etc/passwd”;而另一個(gè)文件包含這樣的聲明: char *filename;盡管在某些環(huán)境中數(shù)組和指針的行為非常相似,但它們是不同的。在第一個(gè)聲明中,filename是一個(gè)字符數(shù)組的名字。盡管使用數(shù)組的名字可以產(chǎn)生數(shù)組第一個(gè)元素的指針,但這個(gè)指針只有在需要的時(shí)候才產(chǎn)生并且不會持續(xù)。在第二個(gè)聲明中,filename是一個(gè)指針的名字。這個(gè)指針可以指向程序員讓它指向的任何地方。如果程序員沒有給它賦一個(gè)值,它將具有一個(gè)默認(rèn)的0值(null)[譯注:實(shí)際上,在C中一個(gè)為初始化的指針通常具有一個(gè)隨機(jī)的值,這是很危險(xiǎn)的!]。這兩個(gè)聲明以不同的方式使用存儲區(qū),他們不可能共存。避免這種類型沖突的一個(gè)方法是使用像lint這樣的工具(如果可以的話)。為了在一個(gè)程序的不同編譯單元之間檢查類型沖突,一些程序需要一次看到其所有部分。典型的編譯器無法完成,但lint可以。避免該問題的另一種方法是將外部聲明放到包含文件中。這時(shí),一個(gè)外部對象的類型僅出現(xiàn)一次[7]。4 語義缺陷 一個(gè)句子可以是精確拼寫的并且沒有語法錯(cuò)誤,但仍然沒有意義。在這一節(jié)中,我們將會看到一些程序的寫法會使得它們看起來是一個(gè)意思,但實(shí)際上是另一種完全不同的意思。我們還要討論一些表面上看起來合理但實(shí)際上會產(chǎn)生未定義結(jié)果的環(huán)境。我們這里討論的東西并不保證能夠在所有的C實(shí)現(xiàn)中工作。我們暫且忘記這些能夠在一些實(shí)現(xiàn)中工作但可能不能在另一些實(shí)現(xiàn)中工作的東西,直到第7節(jié)討論可以執(zhí)行問題為止。4.1 表達(dá)式求值順序 一些C運(yùn)算符以一種已知的、特定的順序?qū)ζ洳僮鲾?shù)進(jìn)行求值。但另一些不能。例如,考慮下面的表達(dá)式: a < b && c < d C語言定義規(guī)定a < b首先被求值。如果a確實(shí)小于b,c < d必須緊接著被求值以計(jì)算整個(gè)表達(dá)式的值。但如果a大于或等于b,則c < d根本不會被求值。要對a < b求值,編譯器對a和b的求值就會有一個(gè)先后。但在一些機(jī)器上,它們也許是并行進(jìn)行的。C中只有四個(gè)運(yùn)算符&&、||、?:和,指定了求值順序。&&和||最先對左邊的操作數(shù)進(jìn)行求值,而右邊的操作數(shù)只有在需要的時(shí)候才進(jìn)行求值。而?:運(yùn)算符中的三個(gè)操作數(shù):a、b和c,最先對a進(jìn)行求值,之后僅對b或c中的一個(gè)進(jìn)行求值,這取決于a的值。,運(yùn)算符首先對左邊的操作數(shù)進(jìn)行求值,然后拋棄它的值,對右邊的操作數(shù)進(jìn)行求值[8]。C中所有其它的運(yùn)算符對操作數(shù)的求值順序都是未定義的。事實(shí)上,賦值運(yùn)算符不對求值順序做出任何保證。出于這個(gè)原因,下面這種將數(shù)組x中的前n個(gè)元素復(fù)制到數(shù)組y中的方法是不可行的: i = 0;while(i < n)y = x[i++];其中的問題是y的地址并不保證在i增長之前被求值。在某些實(shí)現(xiàn)中,這是可能的;但在另一些實(shí)現(xiàn)中卻不可能。另一種情況出于同樣的原因會失敗: i = 0;while(i < n)y[i++] = x;而下面的代碼是可以工作的: i = 0;while(i < n){ y = x;i++;} 當(dāng)然,這可以簡寫為: for(i = 0;i < n;i++)y = x;4.2 &&、||和!運(yùn)算符 C中有兩種邏輯運(yùn)算符,在某些情況下是可以交換的:按位運(yùn)算符&、|和~,以及邏輯運(yùn)算符&&、||和!。一個(gè)程序員如果用某一類運(yùn)算符替換相應(yīng)的另一類運(yùn)算符會得到某些奇怪的效果:程序可能會正確地工作,但這純屬偶然。&&、||和!運(yùn)算符將它們的參數(shù)視為僅有“真”或“假”,通常約定0代表“假”而其它的任意值都代表“真”。這些運(yùn)算符返回1表示“真”而返回0表示“假”,而且&&和||運(yùn)算符當(dāng)可以通過左邊的操作數(shù)確定其返回值時(shí),就不會對右邊的操作數(shù)進(jìn)行求值。因此!10是零,因?yàn)?0非零;10 && 12是1,因?yàn)?0和12都非零;10 || 12也是1,因?yàn)?0非零。另外,最后一個(gè)表達(dá)式中的12不會被求值,10 || f()中的f()也不會被求值。考慮下面這段用于在一個(gè)表中查找一個(gè)特定元素的程序: i = 0;while(i < tabsize && tab!= x)i++;這段循環(huán)背后的意思是如果i等于tabsize時(shí)循環(huán)結(jié)束,元素未被找到。否則,i包含了元素的索引。假設(shè)這個(gè)例子中的&&不小心被替換為了&,這個(gè)循環(huán)可能仍然能夠工作,但只有兩種幸運(yùn)的情況可以使它停下來。首先,這兩個(gè)操作都是當(dāng)條件為假時(shí)返回0,當(dāng)條件為真時(shí)返回1。只要x和y都是1或0,x & y和x && y都具有相同的值。然而,如果當(dāng)使用了出了1之外的非零值表示“真”時(shí)互換了這兩個(gè)運(yùn)算符,這個(gè)循環(huán)將不會工作。其次,由于數(shù)組元素不會改變,因此越過數(shù)組最后一個(gè)元素進(jìn)一個(gè)位置時(shí)是無害的,循環(huán)會幸運(yùn)地停下來。失誤的程序會越過數(shù)組的結(jié)尾,因?yàn)?不像&&,總是會對所有的操作數(shù)進(jìn)行求值。因此循環(huán)的最后一次獲取tab時(shí)i的值已經(jīng)等于tabsize了。如果tabsize是tab中元素的數(shù)量,則會取到tab中不存在的一個(gè)值。4.3 下標(biāo)從零開始 在很多語言中,具有n個(gè)元素的數(shù)組其元素的號碼和它的下標(biāo)是從1到n嚴(yán)格對應(yīng)的。但在C中不是這樣。一個(gè)具有n個(gè)元素的C數(shù)組中沒有下標(biāo)為n的元素,其中的元素的下標(biāo)是從0到n'a';return c;} 在很多C實(shí)現(xiàn)中,為了減少比實(shí)際計(jì)算還要多的調(diào)用開銷,通常將其實(shí)現(xiàn)為宏: #define toupper(c)((c)>= 'a' &&(c)<= 'z' ?(c)+('A''a')#define tolower(c)((c)+ 'A''a' :(c))#define tolower(c)((c)>= 'A' &&(c)<= 'Z' ?(c)+ 'a''a';return c;} tolower()類似。這個(gè)改變帶來更多的問題,每次使用這些函數(shù)的時(shí)候都會引入函數(shù)調(diào)用開銷。我們的英雄認(rèn)為一些人可能不愿意支付這些開銷,因此他們將這個(gè)宏重命名為: #define _toupper(c)((c)+ 'A''A')這就允許用戶選擇方便或速度。這里面其實(shí)只有一個(gè)問題:伯克利的人們和其他的C實(shí)現(xiàn)者并沒有跟著這么做。這意味著一個(gè)在AT&T系統(tǒng)上編寫的使用了toupper()或tolower()的程序,如果沒有為其傳遞正確大小寫字母參數(shù),在其他C實(shí)現(xiàn)中可能不會正常工作。如果不知道這些歷史,可能很難對這類錯(cuò)誤進(jìn)行跟蹤。7.8 先釋放,再重新分配 很多C實(shí)現(xiàn)為用戶提供了三個(gè)內(nèi)存分配函數(shù):malloc()、realloc()和free()。調(diào)用malloc(n)返回一個(gè)指向有n個(gè)字符的新分配的內(nèi)存的指針,這個(gè)指針可以由程序員使用。給free()傳遞一個(gè)指向由malloc()分配的內(nèi)存的指針可以使這塊內(nèi)存得以重用。通過一個(gè)指向已分配區(qū)域的指針和一個(gè)新的大小調(diào)用realloc()可以將這塊內(nèi)存擴(kuò)大或縮小到新尺寸,這個(gè)過程中可能要復(fù)制內(nèi)存。也許有人會想,真相真是有點(diǎn)微妙啊。下面是System V接口定義中出現(xiàn)的對realloc()的描述: realloc改變一個(gè)由ptr指向的size個(gè)字節(jié)的塊,并返回該塊(可能被移動)的指針。在新舊尺寸中比較小的一個(gè)尺寸之下的內(nèi)容不會被改變。而UNIX系統(tǒng)第七版的參考手冊中包含了這一段的副本。此外,還包含了描述realloc()的另外一段: 如果在最后一次調(diào)用malloc、realloc或calloc后釋放了ptr所指向的塊,realloc依舊可以工作;因此,free、malloc和realloc的順序可以利用malloc壓縮存貯的查找策略。因此,下面的代碼片段在UNIX第七版中是合法的: free(p);p = realloc(p, newsize);這一特性保留在從UNIX第七版衍生出來的系統(tǒng)中:可以先釋放一塊存儲區(qū)域,然后再重新分配它。這意味著,在這些系統(tǒng)中釋放的內(nèi)存中的內(nèi)容在下一次內(nèi)存分配之前可以保證不變。因此,在這些系統(tǒng)中,我們可以用下面這種奇特的思想來釋放一個(gè)鏈表中的所有元素: for(p = head;p!= NULL;p = p->next)free((char *)p);而不用擔(dān)心調(diào)用free()會導(dǎo)致p->next不可用。不用說,這種技術(shù)是不推薦的,因?yàn)椴皇撬蠧實(shí)現(xiàn)都能在內(nèi)存被釋放后將它的內(nèi)容保留足夠長的時(shí)間。然而,第七版的手冊遺留了一個(gè)未聲明的問題:realloc()的原始實(shí)現(xiàn)實(shí)際上是必須要先釋放再重新分配的。出于這個(gè)原因,一些C程序都是先釋放內(nèi)存再重新分配的,而當(dāng)這些程序移植到其他實(shí)現(xiàn)中時(shí)就會出現(xiàn)問題。7.9 可移植性問題的一個(gè)實(shí)例 讓我們來看一個(gè)已經(jīng)被很多人在很多時(shí)候解決了的問題。下面的程序帶有兩個(gè)參數(shù):一個(gè)長整數(shù)和一個(gè)函數(shù)(的指針)。它將整數(shù)轉(zhuǎn)換位十進(jìn)制數(shù),并用代表其中每一個(gè)數(shù)字的字符來調(diào)用給定的函數(shù)。void printnum(long n, void(*p)()){ if(n < 0){(*p)('-');n =-n;} if(n >= 10)printnum(n / 10, p);(*p)(n % 10 + '0');} 這個(gè)程序非常簡單。首先檢查n是否為負(fù)數(shù);如果是,則打印一個(gè)符號并將n變?yōu)檎龜?shù)。接下來,測試是否n >= 10。如果是,則它的十進(jìn)制表示中包含兩個(gè)或更多個(gè)數(shù)字,因此我們遞歸地調(diào)用printnum()來打印除最后一個(gè)數(shù)字外的所有數(shù)字。最后,我們打印最后一個(gè)數(shù)字。這個(gè)程序——由于它的簡單——具有很多可移植性問題。首先是將n的低位數(shù)字轉(zhuǎn)換成字符形式的方法。用n % 10來獲取低位數(shù)字的值是好的,但為它加上'0'來獲得相應(yīng)的字符表示就不好了。這個(gè)加法假設(shè)機(jī)器中順序的數(shù)字所對應(yīng)的字符數(shù)順序的,沒有間隔,因此'0' + 5和'5'的值是相同的,等等。盡管這個(gè)假設(shè)對于ASCII和EBCDIC字符集是成立的,但對于其他一些機(jī)器可能不成立。避免這個(gè)問題的方法是使用一個(gè)表: void printnum(long n, void(*p)()){ if(n < 0){(*p)('-');n =-n;} if(n >= 10)printnum(n / 10, p);(*p)(“0123456789”[n % 10]);} 另一個(gè)問題發(fā)生在當(dāng)n < 0時(shí)。這時(shí)程序會打印一個(gè)負(fù)號并將n設(shè)置為-n。這個(gè)賦值會發(fā)生溢出,因?yàn)樵谑褂?的補(bǔ)碼的機(jī)器上通常能夠表示的負(fù)數(shù)比正數(shù)要多。例如,一個(gè)(長)整數(shù)有k位和一個(gè)附加位表示符號,則-2k可以表示而2k卻不能。解決這一問題有很多方法。最直觀的一種是將n賦給一個(gè)unsigned long值。然而,一些C便一起可能沒有實(shí)現(xiàn)unsigned long,因此我們來看看沒有它怎么辦。在第一個(gè)實(shí)現(xiàn)和第二個(gè)實(shí)現(xiàn)的機(jī)器上,改變一個(gè)正整數(shù)的符號保證不會發(fā)生溢出。問題僅出在改變一個(gè)負(fù)數(shù)的符號時(shí)。因此,我們可以通過避免將n變?yōu)檎龜?shù)來避免這個(gè)問題。當(dāng)然,一旦我們打印了負(fù)數(shù)的符號,我們就能夠?qū)⒇?fù)數(shù)和正數(shù)視為是一樣的。下面的方法就強(qiáng)制在打印符號之后n為負(fù)數(shù),并且用負(fù)數(shù)值完成我們所有的算法。如果我們這么做,我們就必須保證程序中打印符號的部分只執(zhí)行一次;一個(gè)簡單的方法是將這個(gè)程序劃分為兩個(gè)函數(shù): void printnum(long n, void(*p)()){ if(n < 0){(*p)('-');printneg(n, p);} else printneg(-n, p);} void printneg(long n, void(*p)()){ if(n <=-10)printneg(n / 10, p);(*p)(“0123456789”[-(n % 10)]);} printnum()現(xiàn)在只檢查要打印的數(shù)是否為負(fù)數(shù);如果是的話則打印一個(gè)符號。否則,它以n的負(fù)絕對值來調(diào)用printneg()。我們同時(shí)改變了printneg()的函數(shù)體來適應(yīng)n永遠(yuǎn)是負(fù)數(shù)或零這一事實(shí)。我們得到什么?我們使用n / 10和n % 10來獲取n的前導(dǎo)數(shù)字和結(jié)尾數(shù)字(經(jīng)過適當(dāng)?shù)姆栕儞Q)。調(diào)用整數(shù)除法的行為在其中一個(gè)操作數(shù)為負(fù)的時(shí)候是實(shí)現(xiàn)相關(guān)的。因此,n % 10有可能是正的!這時(shí),-(n % 10)是正數(shù),將會超出我們的數(shù)字字符數(shù)組的末尾。為了解決這一問題,我們建立兩個(gè)臨時(shí)變量來存放商和余數(shù)。作完除法后,我們檢查余數(shù)是否在正確的范圍內(nèi),如果不是的話則調(diào)整這兩個(gè)變量。printnum()沒有改變,因此我們只列出printneg(): void printneg(long n, void(*p)()){ long q;int r;if(r > 0){ r-= 10;q++;} if(n <=-10){ printneg(q, p);}(*p)(“0123456789”[-r]);} 8 這里是空閑空間 還有很多可能讓C程序員誤入迷途的地方本文沒有提到。如果你發(fā)現(xiàn)了,請聯(lián)系作者。在以后的版本中它會被包含進(jìn)來,并添加一個(gè)表示感謝的腳注。參考 《The C Programming Language》(Kernighan and Ritchie, Prentice-Hall 1978)是最具權(quán)威的C著作。它包含了一個(gè)優(yōu)秀的教程,面向那些熟悉其他高級語言程序設(shè)計(jì)的人,和一個(gè)參考手冊,簡潔地描述了整個(gè)語言。盡管自1978年以來這門語言發(fā)生了不少變化,這本書對于很多主題來說仍然是個(gè)定論。這本書同時(shí)還包含了本文中多次提到的“C語言參考手冊”。《The C Puzzle Book》(Feuer, Prentice-Hall, 1982)是一本少見的磨煉人們文法能力的書。這本書收集了很多謎題(和答案),它們的解決方法能夠測試讀者對于C語言精妙之處的知識。《C: A Referenct Manual》(Harbison and Steele, Prentice Hall 1984)是特意為實(shí)現(xiàn)者編寫的一本參考資料。其他人也會發(fā)現(xiàn)它是特別有用的——因?yàn)樗軓闹袇⒖技?xì)節(jié)。------------------腳注 1.這本書是基于圖書《C Traps and Pitfalls》(Addison-Wesley, 1989, ISBN 0-201-17928-8)的一個(gè)擴(kuò)充,有興趣的讀者可以讀一讀它。2.因?yàn)?=的結(jié)果不是1就是0。3.感謝Guy Harris為我指出這個(gè)問題。4.Dennis Ritchie和Steve Johnson同時(shí)向我指出了這個(gè)問題。5.感謝一位不知名的志愿者提出這個(gè)問題。6.感謝Richard Stevens指出了這個(gè)問題。7.一些C編譯器要求每個(gè)外部對象僅有一個(gè)定義,但可以有多個(gè)聲明。使用這樣的編譯器時(shí),我們何以很容易地將一個(gè)聲明放到一個(gè)包含文件中,并將其定義放到其它地方。這意味著每個(gè)外部對象的類型將出現(xiàn)兩次,但這比出現(xiàn)多于兩次要好。8.分離函數(shù)參數(shù)用的逗號不是逗號運(yùn)算符。例如在f(x, y)中,x和y的獲取順序是未定義的,但在g((x, y))中不是這樣的。其中g(shù)只有一個(gè)參數(shù)。它的值是通過對x進(jìn)行求值、拋棄這個(gè)值、再對y進(jìn)行求值來確定的。9.預(yù)處理器還可以很容易地組織這樣的顯式常量以能夠方便地找到它們。10.PDP-11和VAX-11是數(shù)組設(shè)備集團(tuán)(DEC)的商標(biāo)。

第二篇:C語言陷阱和缺陷

0 簡介

C語言及其典型實(shí)現(xiàn)被設(shè)計(jì)為能被專家們?nèi)菀椎厥褂谩_@門語言簡潔并附有表達(dá)力。但有一些限制可以保護(hù)那些浮躁的人。一個(gè)浮躁的人可以從這些條款中獲得一些幫助。

在本文中,我們將會看到這些未可知的益處。正是由于它的未可知,我們無法為其進(jìn)行完全的分類。不過,我們?nèi)匀煌ㄟ^研究為了一個(gè)C程序的運(yùn)行所需要做的事來做到這些。我們假設(shè)讀者對C語言至少有個(gè)粗淺的了解。

第一部分研究了當(dāng)程序被劃分為記號時(shí)會發(fā)生的問題。第二部分繼續(xù)研究了當(dāng)程序的記號被編譯器組合為聲明、表達(dá)式和語句時(shí)會出現(xiàn)的問題。第三部分研究了由多個(gè)部分組成、分別編譯并綁定到一起的C程序。第四部分處理了概念上的誤解:當(dāng)一個(gè)程序具體執(zhí)行時(shí)會發(fā)生的事情。第五部分研究了我們的程序和它們所使用的常用庫之間的關(guān)系。在第六部分中,我們注意到了我們所寫的程序也許并不是我們所運(yùn)行的程序;預(yù)處理器將首先運(yùn)行。最后,第七部分討論了可移植性問題:一個(gè)能在一個(gè)實(shí)現(xiàn)中運(yùn)行的程序無法在另一個(gè)實(shí)現(xiàn)中運(yùn)行的原因。詞法缺陷

編譯器的第一個(gè)部分常被稱為詞法分析器(lexical analyzer)。詞法分析器檢查組成程序的字符序列,并將它們劃分為記號(token)一個(gè)記號是一個(gè)由一個(gè)或多個(gè)字符構(gòu)成的序列,它在語言被編譯時(shí)具有一個(gè)(相關(guān)地)統(tǒng)一的意義。在C中,例如,記號->的意義和組成它的每個(gè)獨(dú)立的字符具有明顯的區(qū)別,而且其意義獨(dú)立于->出現(xiàn)的上下文環(huán)境。

另外一個(gè)例子,考慮下面的語句:

if(x > big)big = x;

該語句中的每一個(gè)分離的字符都被劃分為一個(gè)記號,除了關(guān)鍵字if和標(biāo)識符big的兩個(gè)實(shí)例。

事實(shí)上,C程序被兩次劃分為記號。首先是預(yù)處理器讀取程序。它必須對程序進(jìn)行記號劃分以發(fā)現(xiàn)標(biāo)識宏的標(biāo)識符。它必須通過對每個(gè)宏進(jìn)行求值來替換宏調(diào)用。最后,經(jīng)過宏替換的程序又被匯集成字符流送給編譯器。編譯器再第二次將這個(gè)流劃分為記號。

在這一節(jié)中,我們將探索對記號的意義的普遍的誤解以及記號和組成它們的字符之間的關(guān)系。稍后我們將談到預(yù)處理器。

1.1 = 不是==

從Algol派生出來的語言,如Pascal和Ada,用:=表示賦值而用=表示比較。而C語言則是用=表示賦值而用==表示比較。這是因?yàn)橘x值的頻率要高于比較,因此為其分配更短的符號。

此外,C還將賦值視為一個(gè)運(yùn)算符,因此可以很容易地寫出多重賦值(如a = b = c),并且可以將賦值嵌入到一個(gè)大的表達(dá)式中。

這種便捷導(dǎo)致了一個(gè)潛在的問題:可能將需要比較的地方寫成賦值。因此,下面的語句好像看起來是要檢查x是否等于y:

if(x = y)

foo();

而實(shí)際上是將x設(shè)置為y的值并檢查結(jié)果是否非零。再考慮下面的一個(gè)希望跳過空格、制表符和換行符的循環(huán):

while(c == ' ' || c = '/t' || c == '/n')

c = getc(f);

在與'/t'進(jìn)行比較的地方程序員錯(cuò)誤地使用=代替了==。這個(gè)“比較”實(shí)際上是將'/t'賦給c,然后判斷c的(新的)值是否為零。因?yàn)?/t'不為零,這個(gè)“比較”將一直為真,因此這個(gè)循環(huán)會吃盡整個(gè)文件。這之后會發(fā)生什么取決于特定的實(shí)現(xiàn)是否允許一個(gè)程序讀取超過文件尾部的部分。如果允許,這個(gè)循環(huán)會一直運(yùn)行。

一些C編譯器會對形如e1 = e2的條件給出一個(gè)警告以提醒用戶。當(dāng)你確實(shí)需要先對一個(gè)變量進(jìn)行賦值之后再檢查變量是否非零時(shí),為了在這種編譯器中避免警告信息,應(yīng)考慮顯式給出比較符。換句話說,將: if(x = y)

foo();改寫為:

if((x = y)!= 0)

foo();

這樣可以清晰地表示你的意圖。

1.2 & 和 | 不是 && 和||

容易將==錯(cuò)寫為=是因?yàn)楹芏嗥渌Z言使用=表示比較運(yùn)算。其他容易寫錯(cuò)的運(yùn)算符還有&和&&,以及|和||,這主要是因?yàn)镃語言中的&和|運(yùn)算符于其他語言中具有類似功能的運(yùn)算符大為不同。我們將在第4節(jié)中貼近地觀察這些運(yùn)算符。

1.3 多字符記號

一些C記號,如/、*和=只有一個(gè)字符。而其他一些C記號,如/*和==,以及標(biāo)識符,具有多個(gè)字符。當(dāng)C編譯器遇到緊連在一起的/和*時(shí),它必須能夠決定是將這兩個(gè)字符識別為兩個(gè)分離的記號還是一個(gè)單獨(dú)的記號。C語言參考手冊說明了如何決定:“如果輸入流到一個(gè)給定的字符串為止已經(jīng)被識別為記號,則應(yīng)該包含下一個(gè)字符以組成能夠構(gòu)成記號的最長的字符串”([譯注]即通常所說的“最長子串原則”)。因此,如果/是一個(gè)記號的第一個(gè)字符,并且/后面緊隨了一個(gè)*,則這兩個(gè)字符構(gòu)成了注釋的開始,不管其他上下文環(huán)境。

下面的語句看起來像是將y的值設(shè)置為x的值除以p所指向的值: y = x/*p

/* p 指向除數(shù) */;

實(shí)際上,/*開始了一個(gè)注釋,因此編譯器簡單地吞噬程序文本,直到*/的出現(xiàn)。換句話說,這條語句僅僅把y的值設(shè)置為x的值,而根本沒有看到p。將這條語句重寫為: y = x / *p

/* p 指向除數(shù) */;或者干脆是

y = x /(*p)

/* p指向除數(shù) */;它就可以做注釋所暗示的除法了。

這種模棱兩可的寫法在其他環(huán)境中就會引起麻煩。例如,老版本的C使用=+表示現(xiàn)在版本中的+=。這樣的編譯器會將 a=-1;視為 a =-1;或

a = a> a

是不合法的。它和

p-> a

不是同義詞。

另一方面,有些老式編譯器還是將=+視為一個(gè)單獨(dú)的記號并且和+=是同義詞。

1.5 字符串和字符

單引號和雙引號在C中的意義完全不同,在一些混亂的上下文中它們會導(dǎo)致奇怪的結(jié)果而不是錯(cuò)誤消息。

包圍在單引號中的一個(gè)字符只是編寫整數(shù)的另一種方法。這個(gè)整數(shù)是給定的字符在實(shí)現(xiàn)的對照序列中的一個(gè)對應(yīng)的值。因此,在一個(gè)ASCII實(shí)現(xiàn)中,'a'和0141或97表示完全相同的東西。而一個(gè)包圍在雙引號中的字符串,只是編寫一個(gè)有雙引號之間的字符和一個(gè)附加的二進(jìn)制值為零的字符所初始化的一個(gè)無名數(shù)組的指針的一種簡短方法。

下面的兩個(gè)程序片斷是等價(jià)的:

printf(“Hello world/n”);

char hello[] = { 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '/n', 0 };printf(hello);

使用一個(gè)指針來代替一個(gè)整數(shù)通常會得到一個(gè)警告消息(反之亦然),使用雙引號來代替單引號也會得到一個(gè)警告消息(反之亦然)。但對于不檢查參數(shù)類型的編譯器卻除外。因此,用 printf('/n');來代替 printf(“/n”);通常會在運(yùn)行時(shí)得到奇怪的結(jié)果。([譯注]提示:正如上面所說,'/n'表示一個(gè)整數(shù),它被轉(zhuǎn)換為了一個(gè)指針,這個(gè)指針?biāo)赶虻膬?nèi)容是沒有意義的。)

由于一個(gè)整數(shù)通常足夠大,以至于能夠放下多個(gè)字符,一些C編譯器允許在一個(gè)字符常量中存放多個(gè)字符。這意味著用'yes'代替“yes”將不會被發(fā)現(xiàn)。后者意味著“分別包含y、e、s和一個(gè)空字符的四個(gè)連續(xù)存儲器區(qū)域中的第一個(gè)的地址”,而前者意味著“在一些實(shí)現(xiàn)定義的樣式中表示由字符y、e、s聯(lián)合構(gòu)成的一個(gè)整數(shù)”。這兩者之間的任何一致性都純屬巧合。句法缺陷

要理解C語言程序,僅了解構(gòu)成它的記號是不夠的。還要理解這些記號是如何構(gòu)成聲明、表達(dá)式、語句和程序的。盡管這些構(gòu)成通常都是定義良好的,但這些定義有時(shí)候是有悖于直覺的或混亂的。

在這一節(jié)中,我們將著眼于一些不明顯句法構(gòu)造。

2.1 理解聲明

我曾經(jīng)和一些人聊過天,他們那時(shí)正在在編寫在一個(gè)小型的微處理器上單機(jī)運(yùn)行的C程序。當(dāng)這臺機(jī)器的開關(guān)打開的時(shí)候,硬件會調(diào)用地址為0處的子程序。

為了模仿電源打開的情形,我們要設(shè)計(jì)一條C語句來顯式地調(diào)用這個(gè)子程序。經(jīng)過一些思考,我們寫出了下面的語句:

(*(void(*)())0)();

這樣的表達(dá)式會令C程序員心驚膽戰(zhàn)。但是,并不需要這樣,因?yàn)樗麄兛梢栽谝粋€(gè)簡單的規(guī)則的幫助下很容易地構(gòu)造它:以你使用的方式聲明它。

每個(gè)C變量聲明都具有兩個(gè)部分:一個(gè)類型和一組具有特定格式的、期望用來對該類型求值的表達(dá)式。最簡單的表達(dá)式就是一個(gè)變量:

float f, g;

說明表達(dá)式f和g——在求值的時(shí)候——具有類型float。由于待求值的是表達(dá)式,因此可以自由地使用圓括號:

float((f));

這表示((f))求值為float并且因此,通過推斷,f也是一個(gè)float。

同樣的邏輯用在函數(shù)和指針類型。例如:

float ff();

表示表達(dá)式ff()是一個(gè)float,因此ff是一個(gè)返回一個(gè)float的函數(shù)。類似地,float *pf;

表示*pf是一個(gè)float并且因此pf是一個(gè)指向一個(gè)float的指針。

這些形式的組合聲明對表達(dá)式是一樣的。因此,float *g(),(*h)();

表示*g()和(*h)()都是float表達(dá)式。由于()比*綁定得更緊密,*g()和*(g())表示同樣的東西:g是一個(gè)返回指float指針的函數(shù),而h是一個(gè)指向返回float的函數(shù)的指針。

當(dāng)我們知道如何聲明一個(gè)給定類型的變量以后,就能夠很容易地寫出一個(gè)類型的模型(cast):只要?jiǎng)h除變量名和分號并將所有的東西包圍在一對圓括號中即可。因此,由于 float *g();

聲明g是一個(gè)返回float指針的函數(shù),所以(float *())就是它的模型。

有了這些知識的武裝,我們現(xiàn)在可以準(zhǔn)備解決(*(void(*)())0)()了。我們可以將它分為兩個(gè)部分進(jìn)行分析。首先,假設(shè)我們有一個(gè)變量fp,它包含了一個(gè)函數(shù)指針,并且我們希望調(diào)用fp所指向的函數(shù)。可以這樣寫:

(*fp)();

如果fp是一個(gè)指向函數(shù)的指針,則*fp就是函數(shù)本身,因此(*fp)()是調(diào)用它的一種方法。(*fp)中的括號是必須的,否則這個(gè)表達(dá)式將會被分析為*(fp())。我們現(xiàn)在要找一個(gè)適當(dāng)?shù)谋磉_(dá)式來替換fp。

這個(gè)問題就是我們的第二步分析。如果C可以讀入并理解類型,我們可以寫:(*0)();

但這樣并不行,因?yàn)?運(yùn)算符要求必須有一個(gè)指針作為它的操作數(shù)。另外,這個(gè)操作數(shù)必須是一個(gè)指向函數(shù)的指針,以保證*的結(jié)果可以被調(diào)用。因此,我們需要將0轉(zhuǎn)換為一個(gè)可以描述“指向一個(gè)返回void的函數(shù)的指針”的類型。

如果fp是一個(gè)指向返回void的函數(shù)的指針,則(*fp)()是一個(gè)void值,并且它的聲明將會是這樣的: void(*fp)();

因此,我們需要寫:

void(*fp)();(*fp)();

來聲明一個(gè)啞變量。一旦我們知道了如何聲明該變量,我們也就知道了如何將一個(gè)常數(shù)轉(zhuǎn)換為該類型:只要從變量的聲明中去掉名字即可。因此,我們像下面這樣將0轉(zhuǎn)換為一個(gè)“指向返回void的函數(shù)的指針”:

(void(*)())0

接下來,我們用(void(*)())0來替換fp:

(*(void(*)())0)();

結(jié)尾處的分號用于將這個(gè)表達(dá)式轉(zhuǎn)換為一個(gè)語句。

在這里,我們解決這個(gè)問題時(shí)沒有使用typedef聲明。通過使用它,我們可以更清晰地解決這個(gè)問題:

typedef void(*funcptr)();(*(funcptr)0)();

2.2 運(yùn)算符并不總是具有你所想象的優(yōu)先級

假設(shè)有一個(gè)聲明了的常量FLAG,它是一個(gè)整數(shù),其二進(jìn)制表示中的某一位被置位(換句話說,它是2的某次冪),并且你希望測試一個(gè)整型變量flags該位是否被置位。通常的寫法是:

if(flags & FLAG)...其意義對于很多C程序員都是很明確的:if語句測試?yán)ㄌ栔械谋磉_(dá)式求值的結(jié)果是否為0。出于清晰的目的我們可以將它寫得更明確:

if(flags & FLAG!= 0)...這個(gè)語句現(xiàn)在更容易理解了。但它仍然是錯(cuò)的,因?yàn)?=比&綁定得更緊密,因此它被分析為: if(flags &(FLAG!= 0))...這(偶爾)是可以的,如FLAG是1或0(!)的時(shí)候,但對于其他2的冪是不行的[2]。

假設(shè)你有兩個(gè)整型變量,h和l,它們的值在0和15(含0和15)之間,并且你希望將r設(shè)置為8位值,其低位為l,高位為h。一種自然的寫法是: r = h << 4 + 1;不幸的是,這是錯(cuò)誤的。加法比移位綁定得更緊密,因此這個(gè)例子等價(jià)于: r = h <<(4 + l);正確的方法有兩種:

r =(h << 4)+ l;r = h << 4 | l;

避免這種問題的一個(gè)方法是將所有的東西都用括號括起來,但表達(dá)式中的括號過度就會難以理解,因此最好還是是記住C中的優(yōu)先級。

不幸的是,這有15個(gè),太困難了。然而,通過將它們分組可以變得容易。

綁定得最緊密的運(yùn)算符并不是真正的運(yùn)算符:下標(biāo)、函數(shù)調(diào)用和結(jié)構(gòu)選擇。這些都與左邊相關(guān)聯(lián)。

接下來是一元運(yùn)算符。它們具有真正的運(yùn)算符中的最高優(yōu)先級。由于函數(shù)調(diào)用比一元運(yùn)算符綁定得更緊密,你必須寫(*p)()來調(diào)用p指向的函數(shù);*p()表示p是一個(gè)返回一個(gè)指針的函數(shù)。轉(zhuǎn)換是一元運(yùn)算符,并且和其他一元運(yùn)算符具有相同的優(yōu)先級。一元運(yùn)算符是右結(jié)合的,因此*p++表示*(p++),而不是(*p)++。

在接下來是真正的二元運(yùn)算符。其中數(shù)學(xué)運(yùn)算符具有最高的優(yōu)先級,然后是移位運(yùn)算符、關(guān)系運(yùn)算符、邏輯運(yùn)算符、賦值運(yùn)算符,最后是條件運(yùn)算符。需要記住的兩個(gè)重要的東西是:

? 所有的邏輯運(yùn)算符具有比所有關(guān)系運(yùn)算符都低的優(yōu)先級。

? 移位運(yùn)算符比關(guān)系運(yùn)算符綁定得更緊密,但又不如數(shù)學(xué)運(yùn)算符。

在這些運(yùn)算符類別中,有一些奇怪的地方。乘法、除法和求余具有相同的優(yōu)先級,加法和減法具有相同的優(yōu)先級,以及移位運(yùn)算符具有相同的優(yōu)先級。

還有就是六個(gè)關(guān)系運(yùn)算符并不具有相同的優(yōu)先級:==和!=的優(yōu)先級比其他關(guān)系運(yùn)算符要低。這就允許我們判斷a和b是否具有與c和d相同的順序,例如:

a < b == c < d

在邏輯運(yùn)算符中,沒有任何兩個(gè)具有相同的優(yōu)先級。按位運(yùn)算符比所有順序運(yùn)算符綁定得都緊密,每種與運(yùn)算符都比相應(yīng)的或運(yùn)算符綁定得更緊密,并且按位異或(^)運(yùn)算符介于按位與和按位或之間。

三元運(yùn)算符的優(yōu)先級比我們提到過的所有運(yùn)算符的優(yōu)先級都低。這可以保證選擇表達(dá)式中包含的關(guān)系運(yùn)算符的邏輯組合特性,如:

z = a < b && b < c ? d : e

這個(gè)例子還說明了賦值運(yùn)算符具有比條件運(yùn)算符更低的優(yōu)先級是有意義的。另外,所有的復(fù)合賦值運(yùn)算符具有相同的優(yōu)先級并且是自右至左結(jié)合的,因此 a = b = c 和

b = c;a = b;是等價(jià)的。

具有最低優(yōu)先級的是逗號運(yùn)算符。這很容易理解,因?yàn)槎禾柾ǔT谛枰磉_(dá)式而不是語句的時(shí)候用來替代分號。

賦值是另一種運(yùn)算符,通常具有混合的優(yōu)先級。例如,考慮下面這個(gè)用于復(fù)制文件的循環(huán):

while(c = getc(in)!= EOF)

putc(c, out);

這個(gè)while循環(huán)中的表達(dá)式看起來像是c被賦以getc(in)的值,接下來判斷是否等于EOF以結(jié)束循環(huán)。不幸的是,賦值的優(yōu)先級比任何比較操作都低,因此c的值將會是getc(in)和EOF比較的結(jié)果,并且會被拋棄。因此,“復(fù)制”得到的文件將是一個(gè)由值為1的字節(jié)流組成的文件。

上面這個(gè)例子正確的寫法并不難:

while((c = getc(in))!= EOF)

putc(c, out);

然而,這種錯(cuò)誤在很多復(fù)雜的表達(dá)式中卻很難被發(fā)現(xiàn)。例如,隨UNIX系統(tǒng)一同發(fā)布的lint程序通常帶有下面的錯(cuò)誤行:

if(((t = BTYPE(pt1->aty)== STRTY)|| t == UNIONTY){

這條語句希望給t賦一個(gè)值,然后看t是否與STRTY或UNIONTY相等。而實(shí)際的效果卻大不相同[3]。

C中的邏輯運(yùn)算符的優(yōu)先級具有歷史原因。B語言——C的前輩——具有和C中的&和|運(yùn)算符對應(yīng)的邏輯運(yùn)算符。盡管它們的定義是按位的,但編譯器在條件判斷上下文中將它們視為和&&和||一樣。當(dāng)在C中將它們分開后,優(yōu)先級的改變是很危險(xiǎn)的[4]。

2.3 看看這些分號!

C中的一個(gè)多余的分號通常會帶來一點(diǎn)點(diǎn)不同:或者是一個(gè)空語句,無任何效果;或者編譯器可能提出一個(gè)診斷消息,可以方便除去掉它。一個(gè)重要的區(qū)別是在必須跟有一個(gè)語句的if和while語句中。考慮下面的例子: if(x[i] > big);

big = x[i];

這不會發(fā)生編譯錯(cuò)誤,但這段程序的意義與: if(x[i] > big)

big = x[i];

就大不相同了。第一個(gè)程序段等價(jià)于: if(x[i] > big){ } big = x[i];也就是等價(jià)于:

big = x[i];

(除非x、i或big是帶有副作用的宏)。

另一個(gè)因分號引起巨大不同的地方是函數(shù)定義前面的結(jié)構(gòu)聲明的末尾([譯注]這句話不太好聽,看例子就明白了)。考慮下面的程序片段: struct foo {

int x;}

f(){

...}

在緊挨著f的第一個(gè)}后面丟失了一個(gè)分號。它的效果是聲明了一個(gè)函數(shù)f,返回值類型是struct foo,這個(gè)結(jié)構(gòu)成了函數(shù)聲明的一部分。如果這里出現(xiàn)了分號,則f將被定義為具有默認(rèn)的整型返回值[5]。

2.4 switch語句

通常C中的switch語句中的case段可以進(jìn)入下一個(gè)。例如,考慮下面的C和Pascal程序片斷:

switch(color){ case 1: printf(“red”);

break;case 2: printf(“yellow”);

break;case 3: printf(“blue”);

break;}

case color of 1: write('red');2: write('yellow');3: write('blue');end

這兩個(gè)程序片段都作相同的事情:根據(jù)變量color的值是1、2還是3打印red、yellow或blue(沒有新行符)。這兩個(gè)程序片段非常相似,只有一點(diǎn)不同:Pascal程序中沒有C中相應(yīng)的break語句。C中的case標(biāo)簽是真正的標(biāo)簽:控制流程可以無限制地進(jìn)入到一個(gè)case標(biāo)簽中。

看看另一種形式,假設(shè)C程序段看起來更像Pascal:

switch(color){ case 1: printf(“red”);case 2: printf(“yellow”);case 3: printf(“blue”);}

并且假設(shè)color的值是2。則該程序?qū)⒋蛴ellowblue,因?yàn)榭刂谱匀坏剞D(zhuǎn)入到下一個(gè)printf()的調(diào)用。

這既是C語言switch語句的優(yōu)點(diǎn)又是它的弱點(diǎn)。說它是弱點(diǎn),是因?yàn)楹苋菀淄浺粋€(gè)break語句,從而導(dǎo)致程序出現(xiàn)隱晦的異常行為。說它是優(yōu)點(diǎn),是因?yàn)橥ㄟ^故意去掉break語句,可以很容易實(shí)現(xiàn)其他方法難以實(shí)現(xiàn)的控制結(jié)構(gòu)。尤其是在一個(gè)大型的switch語句中,我們經(jīng)常發(fā)現(xiàn)對一個(gè)case的處理可以簡化其他一些特殊的處理。

例如,設(shè)想有一個(gè)程序是一臺假想的機(jī)器的翻譯器。這樣的一個(gè)程序可能包含一個(gè)switch語句來處理各種操作碼。在這樣一臺機(jī)器上,通常減法在對其第二個(gè)運(yùn)算數(shù)進(jìn)行變號后就變成和加法一樣了。因此,最好可以寫出這樣的語句:

case SUBTRACT:

opnd2 =-opnd2;

/* no break;*/ case ADD:

...另外一個(gè)例子,考慮編譯器通過跳過空白字符來查找一個(gè)記號。這里,我們將空格、制表符和新行符視為是相同的,除了新行符還要引起行計(jì)數(shù)器的增長外: case '/n':

linecount++;

/* no break */ case '/t': case ' ':

...2.5 函數(shù)調(diào)用

和其他程序設(shè)計(jì)語言不同,C要求一個(gè)函數(shù)調(diào)用必須有一個(gè)參數(shù)列表,但可以沒有參數(shù)。因此,如果f是一個(gè)函數(shù),f();

就是對該函數(shù)進(jìn)行調(diào)用的語句,而

f;

什么也不做。它會作為函數(shù)地址被求值,但不會調(diào)用它[6]。

2.6 懸掛else問題

在討論任何語法缺陷時(shí)我們都不會忘記提到這個(gè)問題。盡管這一問題不是C語言所獨(dú)有的,但它仍然傷害著那些有著多年經(jīng)驗(yàn)的C程序員。

考慮下面的程序片斷:

if(x == 0)

if(y == 0)error();else {

z = x + y;

f(&z);}

寫這段程序的程序員的目的明顯是將情況分為兩種:x = 0和x!= 0。在第一種情況中,程序段什么都不做,除非y = 0時(shí)調(diào)用error()。第二種情況中,程序設(shè)置z = x + y并以z的地址作為參數(shù)調(diào)用f()。

然而,這段程序的實(shí)際效果卻大為不同。其原因是一個(gè)else總是與其最近的if相關(guān)聯(lián)。如果我們希望這段程序能夠按照實(shí)際的情況運(yùn)行,應(yīng)該這樣寫:

if(x == 0){

if(y == 0)

error();

else {

z = x + y;

f(&z);

} }

換句話說,當(dāng)x!= 0發(fā)生時(shí)什么也不做。如果要達(dá)到第一個(gè)例子的效果,應(yīng)該寫: if(x == 0){

if(y ==0)

error();} else {

z = z + y;

f(&z);} 3 連接

一個(gè)C程序可能有很多部分組成,它們被分別編譯,并由一個(gè)通常稱為連接器、連接編輯器或加載器的程序綁定到一起。由于編譯器一次通常只能看到一個(gè)文件,因此它無法檢測到需要程序的多個(gè)源文件的內(nèi)容才能發(fā)現(xiàn)的錯(cuò)誤。

在這一節(jié)中,我們將看到一些這種類型的錯(cuò)誤。有一些C實(shí)現(xiàn),但不是所有的,帶有一個(gè)稱為lint的程序來捕獲這些錯(cuò)誤。如果具有一個(gè)這樣的程序,那么無論怎樣地強(qiáng)調(diào)它的重要性都不過分。

3.1 你必須自己檢查外部類型

假設(shè)你有一個(gè)C程序,被劃分為兩個(gè)文件。其中一個(gè)包含如下聲明: int n;

而令一個(gè)包含如下聲明:

long n;

這不是一個(gè)有效的C程序,因?yàn)橐恍┩獠棵Q在兩個(gè)文件中被聲明為不同的類型。然而,很多實(shí)現(xiàn)檢測不到這個(gè)錯(cuò)誤,因?yàn)榫幾g器在編譯其中一個(gè)文件時(shí)并不知道另一個(gè)文件的內(nèi)容。因此,檢查類型的工作只能由連接器(或一些工具程序如lint)來完成;如果操作系統(tǒng)的連接器不能識別數(shù)據(jù)類型,C編譯器也沒法過多地強(qiáng)制它。

那么,這個(gè)程序運(yùn)行時(shí)實(shí)際會發(fā)生什么?這有很多可能性:

? 實(shí)現(xiàn)足夠聰明,能夠檢測到類型沖突。則我們會得到一個(gè)診斷消息,說明n在兩個(gè)文件中具有不同的類型。

? 你所使用的實(shí)現(xiàn)將int和long視為相同的類型。典型的情況是機(jī)器可以自然地進(jìn)行32位運(yùn)算。在這種情況下你的程序或許能夠工作,好象你兩次都將變量聲明為long(或int)。但這種程序的工作純屬偶然。

? n的兩個(gè)實(shí)例需要不同的存儲,它們以某種方式共享存儲區(qū),即對其中一個(gè)的賦值對另一個(gè)也有效。這可能發(fā)生,例如,編譯器可以將int安排在long的低位。不論這是基于系統(tǒng)的還是基于機(jī)器的,這種程序的運(yùn)行同樣是偶然。

? n的兩個(gè)實(shí)例以另一種方式共享存儲區(qū),即對其中一個(gè)賦值的效果是對另一個(gè)賦以不同的值。在這種情況下,程序可能失敗。

這種情況發(fā)生的里一個(gè)例子出奇地頻繁。程序的某一個(gè)文件包含下面的聲明: char filename[] = “etc/passwd”;而另一個(gè)文件包含這樣的聲明:

char *filename;

盡管在某些環(huán)境中數(shù)組和指針的行為非常相似,但它們是不同的。在第一個(gè)聲明中,filename是一個(gè)字符數(shù)組的名字。盡管使用數(shù)組的名字可以產(chǎn)生數(shù)組第一個(gè)元素的指針,但這個(gè)指針只有在需要的時(shí)候才產(chǎn)生并且不會持續(xù)。在第二個(gè)聲明中,filename是一個(gè)指針的名字。這個(gè)指針可以指向程序員讓它指向的任何地方。如果程序員沒有給它賦一個(gè)值,它將具有一個(gè)默認(rèn)的0值(NULL)([譯注]實(shí)際上,在C中一個(gè)為初始化的指針通常具有一個(gè)隨機(jī)的值,這是很危險(xiǎn)的!)。

這兩個(gè)聲明以不同的方式使用存儲區(qū),它們不可能共存。

避免這種類型沖突的一個(gè)方法是使用像lint這樣的工具(如果可以的話)。為了在一個(gè)程序的不同編譯單元之間檢查類型沖突,一些程序需要一次看到其所有部分。典型的編譯器無法完成,但lint可以。

避免該問題的另一種方法是將外部聲明放到包含文件中。這時(shí),一個(gè)外部對象的類型僅出現(xiàn)一次[7]。語義缺陷

一個(gè)句子可以是精確拼寫的并且沒有語法錯(cuò)誤,但仍然沒有意義。在這一節(jié)中,我們將會看到一些程序的寫法會使得它們看起來是一個(gè)意思,但實(shí)際上是另一種完全不同的意思。

我們還要討論一些表面上看起來合理但實(shí)際上會產(chǎn)生未定義結(jié)果的環(huán)境。我們這里討論的東西并不保證能夠在所有的C實(shí)現(xiàn)中工作。我們暫且忘記這些能夠在一些實(shí)現(xiàn)中工作但可能不能在另一些實(shí)現(xiàn)中工作的東西,直到第7節(jié)討論可以執(zhí)行問題為止。

4.1 表達(dá)式求值順序

一些C運(yùn)算符以一種已知的、特定的順序?qū)ζ洳僮鲾?shù)進(jìn)行求值。但另一些不能。例如,考慮下面的表達(dá)式:

a < b && c < d

C語言定義規(guī)定a < b首先被求值。如果a確實(shí)小于b,c < d必須緊接著被求值以計(jì)算整個(gè)表達(dá)式的值。但如果a大于或等于b,則c < d根本不會被求值。

要對a < b求值,編譯器對a和b的求值就會有一個(gè)先后。但在一些機(jī)器上,它們也許是并行進(jìn)行的。

C中只有四個(gè)運(yùn)算符&&、||、?:和,指定了求值順序。&&和||最先對左邊的操作數(shù)進(jìn)行求值,而右邊的操作數(shù)只有在需要的時(shí)候才進(jìn)行求值。而?:運(yùn)算符中的三個(gè)操作數(shù):a、b和c,最先對a進(jìn)行求值,之后僅對b或c中的一個(gè)進(jìn)行求值,這取決于a的值。,運(yùn)算符首先對左邊的操作數(shù)進(jìn)行求值,然后拋棄它的值,對右邊的操作數(shù)進(jìn)行求值[8]。

C中所有其它的運(yùn)算符對操作數(shù)的求值順序都是未定義的。事實(shí)上,賦值運(yùn)算符不對求值順序做出任何保證。

出于這個(gè)原因,下面這種將數(shù)組x中的前n個(gè)元素復(fù)制到數(shù)組y中的方法是不可行的: i = 0;while(i < n)

y[i] = x[i++];

其中的問題是y[i]的地址并不保證在i增長之前被求值。在某些實(shí)現(xiàn)中,這是可能的;但在另一些實(shí)現(xiàn)中卻不可能。另一種情況出于同樣的原因會失敗: i = 0;while(i < n)

y[i++] = x[i];

而下面的代碼是可以工作的: i = 0;while(i < n){

y[i] = x[i];

i++;}

當(dāng)然,這可以簡寫為: for(i = 0;i < n;i++)

y[i] = x[i];4.2 &&、||和!運(yùn)算符

C中有兩種邏輯運(yùn)算符,在某些情況下是可以交換的:按位運(yùn)算符&、|和~,以及邏輯運(yùn)算符&&、||和!。一個(gè)程序員如果用某一類運(yùn)算符替換相應(yīng)的另一類運(yùn)算符會得到某些奇怪的效果:程序可能會正確地工作,但這純屬偶然。

&&、||和!運(yùn)算符將它們的參數(shù)視為僅有“真”或“假”,通常約定0代表“假”而其它的任意值都代表“真”。這些運(yùn)算符返回1表示“真”而返回0表示“假”,而且&&和||運(yùn)算符當(dāng)可以通過左邊的操作數(shù)確定其返回值時(shí),就不會對右邊的操作數(shù)進(jìn)行求值。

因此!10是零,因?yàn)?0非零;10 && 12是1,因?yàn)?0和12都非零;10 || 12也是1,因?yàn)?0非零。另外,最后一個(gè)表達(dá)式中的12不會被求值,10 || f()中的f()也不會被求值。

考慮下面這段用于在一個(gè)表中查找一個(gè)特定元素的程序:

i = 0;while(i < tabsize && tab[i]!= x)

i++;

這段循環(huán)背后的意思是如果i等于tabsize時(shí)循環(huán)結(jié)束,元素未被找到。否則,i包含了元素的索引。

假設(shè)這個(gè)例子中的&&不小心被替換為了&,這個(gè)循環(huán)可能仍然能夠工作,但只有兩種幸運(yùn)的情況可以使它停下來。

首先,這兩個(gè)操作都是當(dāng)條件為假時(shí)返回0,當(dāng)條件為真時(shí)返回1。只要x和y都是1或0,x & y和x && y都具有相同的值。然而,如果當(dāng)使用了除1之外的非零值表示“真”時(shí)互換了這兩個(gè)運(yùn)算符,這個(gè)循環(huán)將不會工作。

其次,由于數(shù)組元素不會改變,因此越過數(shù)組最后一個(gè)元素前進(jìn)一個(gè)位置時(shí)是無害的,循環(huán)會幸運(yùn)地停下來。失誤的程序會越過數(shù)組的結(jié)尾,因?yàn)?不像&&,總是會對所有的操作數(shù)進(jìn)行求值。因此循環(huán)的最后一次獲取tab[i]時(shí)i的值已經(jīng)等于tabsize了。如果tabsize是tab中元素的數(shù)量,則會取到tab中不存在的一個(gè)值。

4.3 下標(biāo)從零開始

在很多語言中,具有n個(gè)元素的數(shù)組其元素的號碼和它的下標(biāo)是從1到n嚴(yán)格對應(yīng)的。但在C中不是這樣。

一個(gè)具有n個(gè)元素的C數(shù)組中沒有下標(biāo)為n的元素,其中的元素的下標(biāo)是從0到n'a';

return c;}

在很多C實(shí)現(xiàn)中,為了減少比實(shí)際計(jì)算還要多的調(diào)用開銷,通常將其實(shí)現(xiàn)為宏:

#define toupper(c)((c)>= 'a' &&(c)<= 'z' ?(c)+('A''a')#define tolower(c)((c)+ 'A''a' :(c))#define tolower(c)((c)>= 'A' &&(c)<= 'Z' ?(c)+ 'a''a';

return c;}

tolower()類似。

這個(gè)改變帶來更多的問題,每次使用這些函數(shù)的時(shí)候都會引入函數(shù)調(diào)用開銷。我們的英雄認(rèn)為一些人可能不愿意支付這些開銷,因此他們將這個(gè)宏重命名為:

#define _toupper(c)((c)+ 'A''A')這就允許用戶選擇方便或速度。

這里面其實(shí)只有一個(gè)問題:伯克利的人們和其他的C實(shí)現(xiàn)者并沒有跟著這么做。這意味著一個(gè)在AT&T系統(tǒng)上編寫的使用了toupper()或tolower()的程序,如果沒有為其傳遞正確大小寫字母參數(shù),在其他C實(shí)現(xiàn)中可能不會正常工作。

如果不知道這些歷史,可能很難對這類錯(cuò)誤進(jìn)行跟蹤。

7.8 先釋放,再重新分配

很多C實(shí)現(xiàn)為用戶提供了三個(gè)內(nèi)存分配函數(shù):malloc()、realloc()和free()。調(diào)用malloc(n)返回一個(gè)指向有n個(gè)字符的新分配的內(nèi)存的指針,這個(gè)指針可以由程序員使用。給free()傳遞一個(gè)指向由malloc()分配的內(nèi)存的指針可以使這塊內(nèi)存得以再次使用。通過一個(gè)指向已分配區(qū)域的指針和一個(gè)新的大小調(diào)用realloc()可以將這塊內(nèi)存擴(kuò)大或縮小到新尺寸,這個(gè)過程中可能要復(fù)制內(nèi)存。

也許有人會想,真相真是有點(diǎn)微妙啊。下面是System V接口定義中出現(xiàn)的對realloc()的描述:

realloc改變一個(gè)由ptr指向的size個(gè)字節(jié)的塊,并返回該塊(可能被移動)的指針。在新舊尺寸中比較小的一個(gè)尺寸之下的內(nèi)容不會被改變。

而UNIX系統(tǒng)第七版的參考手冊中包含了這一段的副本。此外,還包含了描述realloc()的另外一段:

如果在最后一次調(diào)用malloc、realloc或calloc后釋放了ptr所指向的塊,realloc依舊可以工作;因此,free、malloc和realloc的順序可以利用malloc壓縮存貯的查找策略。

因此,下面的代碼片段在UNIX第七版中是合法的:

free(p);p = realloc(p, newsize);

這一特性保留在從UNIX第七版衍生出來的系統(tǒng)中:可以先釋放一塊存儲區(qū)域,然后再重新分配它。這意味著,在這些系統(tǒng)中釋放的內(nèi)存中的內(nèi)容在下一次內(nèi)存分配之前可以保證不變。因此,在這些系統(tǒng)中,我們可以用下面這種奇特的思想來釋放一個(gè)鏈表中的所有元素: for(p = head;p!= NULL;p = p->next)

free((char *)p);

而不用擔(dān)心調(diào)用free()會導(dǎo)致p->next不可用。

不用說,這種技術(shù)是不推薦的,因?yàn)椴皇撬蠧實(shí)現(xiàn)都能在內(nèi)存被釋放后將它的內(nèi)容保留足夠長的時(shí)間。然而,第七版的手冊遺留了一個(gè)未聲明的問題:realloc()的原始實(shí)現(xiàn)實(shí)際上是必須要先釋放再重新分配的。出于這個(gè)原因,一些C程序都是先釋放內(nèi)存再重新分配的,而當(dāng)這些程序移植到其他實(shí)現(xiàn)中時(shí)就會出現(xiàn)問題。

7.9 可移植性問題的一個(gè)實(shí)例

讓我們來看一個(gè)已經(jīng)被很多人在很多時(shí)候解決了的問題。下面的程序帶有兩個(gè)參數(shù):一個(gè)長整數(shù)和一個(gè)函數(shù)(的指針)。它將整數(shù)轉(zhuǎn)換位十進(jìn)制數(shù),并用代表其中每一個(gè)數(shù)字的字符來調(diào)用給定的函數(shù)。

void printnum(long n, void(*p)()){

if(n < 0){

(*p)('-');

n =-n;

}

if(n >= 10)

printnum(n / 10, p);

(*p)(n % 10 + '0');}

這個(gè)程序非常簡單。首先檢查n是否為負(fù)數(shù);如果是,則打印一個(gè)符號并將n變?yōu)檎龜?shù)。接下來,測試是否n >= 10。如果是,則它的十進(jìn)制表示中包含兩個(gè)或更多個(gè)數(shù)字,因此我們遞歸地調(diào)用printnum()來打印除最后一個(gè)數(shù)字外的所有數(shù)字。最后,我們打印最后一個(gè)數(shù)字。

這個(gè)程序——由于它的簡單——具有很多可移植性問題。首先是將n的低位數(shù)字轉(zhuǎn)換成字符形式的方法。用n % 10來獲取低位數(shù)字的值是好的,但為它加上'0'來獲得相應(yīng)的字符表示就不好了。這個(gè)加法假設(shè)機(jī)器中順序的數(shù)字所對應(yīng)的字符數(shù)順序的,沒有間隔,因此'0' + 5和'5'的值是相同的,等等。盡管這個(gè)假設(shè)對于ASCII和EBCDIC字符集是成立的,但對于其他一些機(jī)器可能不成立。避免這個(gè)問題的方法是使用一個(gè)表:

void printnum(long n, void(*p)()){

if(n < 0){

(*p)('-');

n =-n;

}

if(n >= 10)

printnum(n / 10, p);

(*p)(“0123456789”[n % 10]);}

另一個(gè)問題發(fā)生在當(dāng)n < 0時(shí)。這時(shí)程序會打印一個(gè)負(fù)號并將n設(shè)置為-n。這個(gè)賦值會發(fā)生溢出,因?yàn)樵谑褂?的補(bǔ)碼的機(jī)器上通常能夠表示的負(fù)數(shù)比正數(shù)要多。例如,一個(gè)(長)整數(shù)有k位和一個(gè)附加位表示符號,則-2k可以表示而2k卻不能。

解決這一問題有很多方法。最直觀的一種是將n賦給一個(gè)unsigned long值。然而,一些C便一起可能沒有實(shí)現(xiàn)unsigned long,因此我們來看看沒有它怎么辦。

在第一個(gè)實(shí)現(xiàn)和第二個(gè)實(shí)現(xiàn)的機(jī)器上,改變一個(gè)正整數(shù)的符號保證不會發(fā)生溢出。問題僅出在改變一個(gè)負(fù)數(shù)的符號時(shí)。因此,我們可以通過避免將n變?yōu)檎龜?shù)來避免這個(gè)問題。

當(dāng)然,一旦我們打印了負(fù)數(shù)的符號,我們就能夠?qū)⒇?fù)數(shù)和正數(shù)視為是一樣的。下面的方法就強(qiáng)制在打印符號之后n為負(fù)數(shù),并且用負(fù)數(shù)值完成我們所有的算法。如果我們這么做,我們就必須保證程序中打印符號的部分只執(zhí)行一次;一個(gè)簡單的方法是將這個(gè)程序劃分為兩個(gè)函數(shù): void printnum(long n, void(*p)()){

if(n < 0){

(*p)('-');

printneg(n, p);

}

else

printneg(-n, p);}

void printneg(long n, void(*p)()){

if(n <=-10)

printneg(n / 10, p);

(*p)(“0123456789”[-(n % 10)]);}

printnum()現(xiàn)在只檢查要打印的數(shù)是否為負(fù)數(shù);如果是的話則打印一個(gè)符號。否則,它以n的負(fù)絕對值來調(diào)用printneg()。我們同時(shí)改變了printneg()的函數(shù)體來適應(yīng)n永遠(yuǎn)是負(fù)數(shù)或零這一事實(shí)。

我們得到什么?我們使用n / 10和n % 10來獲取n的前導(dǎo)數(shù)字和結(jié)尾數(shù)字(經(jīng)過適當(dāng)?shù)姆栕儞Q)。調(diào)用整數(shù)除法的行為在其中一個(gè)操作數(shù)為負(fù)的時(shí)候是實(shí)現(xiàn)相關(guān)的。因此,n % 10有可能是正的!這時(shí),-(n % 10)是負(fù)數(shù),將會超出我們的數(shù)字字符數(shù)組的末尾。

為了解決這一問題,我們建立兩個(gè)臨時(shí)變量來存放商和余數(shù)。作完除法后,我們檢查余數(shù)是否在正確的范圍內(nèi),如果不是的話則調(diào)整這兩個(gè)變量。printnum()沒有改變,因此我們只列出printneg():

void printneg(long n, void(*p)()){

long q;

int r;

if(r > 0){

r-= 10;

q++;

}

if(n <=-10){

printneg(q, p);

}

(*p)(“0123456789”[-r]);} 這里是空閑空間

還有很多可能讓C程序員誤入迷途的地方本文沒有提到。如果你發(fā)現(xiàn)了,請聯(lián)系作者。在以后的版本中它會被包含進(jìn)來,并添加一個(gè)表示感謝的腳注。

參考

《The C Programming Language》(Kernighan and Ritchie, Prentice-Hall 1978)是最具權(quán)威的C著作。它包含了一個(gè)優(yōu)秀的教程,面向那些熟悉其他高級語言程序設(shè)計(jì)的人,和一個(gè)參考手冊,簡潔地描述了整個(gè)語言。盡管自1978年以來這門語言發(fā)生了不少變化,這本書對于很多主題來說仍然是個(gè)定論。這本書同時(shí)還包含了本文中多次提到的“C語言參考手冊”。

《The C Puzzle Book》(Feuer, Prentice-Hall, 1982)是一本少見的磨煉人們文法能力的書。這本書收集了很多謎題(和答案),它們的解決方法能夠測試讀者對于C語言精妙之處的知識。

《C: A Referenct Manual》(Harbison and Steele, Prentice Hall 1984)是特意為實(shí)現(xiàn)者編寫的一本參考資料。其他人也會發(fā)現(xiàn)它是特別有用的——因?yàn)樗軓闹袇⒖技?xì)節(jié)。

腳注

1.這本書是基于圖書《C Traps and Pitfalls》(Addison-Wesley, 1989, ISBN 0-201-17928-8)的一個(gè)擴(kuò)充,有興趣的讀者可以讀一讀它。

2.因?yàn)?=的結(jié)果不是1就是0。

3.感謝Guy Harris為我指出這個(gè)問題。

4.Dennis Ritchie和Steve Johnson同時(shí)向我指出了這個(gè)問題。

5.感謝一位不知名的志愿者提出這個(gè)問題。

6.感謝Richard Stevens指出了這個(gè)問題。

7.一些C編譯器要求每個(gè)外部對象僅有一個(gè)定義,但可以有多個(gè)聲明。使用這樣的編譯器時(shí),我們何以很容易地將一個(gè)聲明放到一個(gè)包含文件中,并將其定義放到其它地方。這意味著每個(gè)外部對象的類型將出現(xiàn)兩次,但這比出現(xiàn)多于兩次要好。

8.分離函數(shù)參數(shù)用的逗號不是逗號運(yùn)算符。例如在f(x, y)中,x和y的獲取順序是未定義的,但在g((x, y))中不是這樣的。其中g(shù)只有一個(gè)參數(shù)。它的值是通過對x進(jìn)行求值、拋棄這個(gè)值、再對y進(jìn)行求值來確定的。

9.預(yù)處理器還可以很容易地組織這樣的顯式常量以能夠方便地找到它們。

10.PDP-11和VAX-11是數(shù)組設(shè)備集團(tuán)(DEC)的商標(biāo)。

本文來自CSDN

客,轉(zhuǎn)

標(biāo)

:http://blog.csdn.net/milan25429688/archive/2005/03/24/328944.aspx#contents

第三篇:語言的功能和陷阱

n

語言的功能和陷阱

n

王蒙

n

一、焦點(diǎn)問題

?

思考語言的社會功能問題,以及由語言而引發(fā)的社會問題。

?

演講的基本特點(diǎn)和要求。

n

二、王蒙其人

王蒙,1934年生于北京

n

5歲上小學(xué)。

n

10歲時(shí)跳級考入中學(xué)。

n

1948年,14歲,王蒙參加地下黨。

n

1949年,15歲調(diào)入新民主主義青年團(tuán)(后改名為共產(chǎn)主義青年團(tuán))北京市委工作。

n

1953年,19歲,長篇小說《青春萬歲》獲得成功。

n

1956年,參加全國第一屆青年作者會議。

n

1956年秋,發(fā)表《組織部來了個(gè)年輕人》,引起極大反響。

n

1958年,24歲,被錯(cuò)劃為右派。

n

1958年,赴北京郊區(qū)勞動。

n

1962年,赴新疆勞動。

n

1963年起在伊犁地區(qū)農(nóng)村勞動多年。

n

n

三、王蒙成就

n

其間曾任自治區(qū)文聯(lián)編輯、維吾爾語翻譯。1979年調(diào)回北京,任北京市文聯(lián)專業(yè)作家。中國作家協(xié)會副主席。

n

自20世紀(jì)50年代以來,發(fā)表作品共一千余萬字。

n

被翻譯成英、法、德、俄、日、韓、意、西班牙、等二十余種語言文字。

n

曾獲意大利蒙德羅文學(xué)獎(jiǎng)、日本創(chuàng)作學(xué)會和平與文化獎(jiǎng)。

n

學(xué)術(shù)著作《〈紅樓夢〉啟示錄》。

n

擔(dān)任十余所大學(xué)教授、名譽(yù)教授、顧問。

n

曾應(yīng)邀訪問世界各大洲四十多個(gè)國家。曾任哈佛大學(xué)燕京學(xué)院特邀訪問學(xué)者、美國三一學(xué)院校長級學(xué)者

n

三、文本分析

n

為什么關(guān)注語言?

“語言是存在的揭示、澄明、到達(dá)”

“語言是存在的家,人就居住在這家中”。

——海德格爾

王蒙這篇演講的前一部分主要講述語言、尤其是文學(xué)語言的基本功能。

?

(一)王蒙提出語言的三種功能:

?

現(xiàn)實(shí)有用的功能;

?

生發(fā)和促進(jìn)的功能(推進(jìn)思想、推進(jìn)感情、推進(jìn)文化、創(chuàng)造文化);

?

浪漫的功能(語言和文字離開了現(xiàn)實(shí)或者超出了現(xiàn)實(shí)的功能)。

?

(二)如何理解王蒙的觀點(diǎn):

?

語言創(chuàng)造了人:(反對語言工具論)提倡語言本體論,如“皎潔”。

?

沒有語言就沒有記憶:(反對語言交際論)提倡語言文化論,如“我們倆困覺”。

?

語言的審美化:(反對語言反映論)提倡語言形象論,如“吃葡萄”。

?

(三)王蒙這篇演講的后一部分主要講述語言的陷阱。

語言的陷阱:

?

語言和現(xiàn)實(shí)和你的思想感情脫節(jié);

?

脫離生活,變成反面的東西;

?

異化、狗屎化效應(yīng)、被語言文字主宰,扼殺創(chuàng)造性,扼殺活潑的生機(jī)。

n

四、怎樣認(rèn)識語言的功能和陷阱

?

(1)語言決定、生產(chǎn)意義。

?

(2)語言是思想的物質(zhì)現(xiàn)實(shí):維特根斯坦說,我的語言的局限就是我的世界的局限。例如,現(xiàn)代“時(shí)間”是一種空間化的隱喻,“自……以來”;但是,在美國印第安的霍皮族那里,沒有昨天、今天、明天的概念。同樣,不同的語言體系生產(chǎn)出不同的“宇宙”,也就有了不同的生命觀、宇宙觀和哲學(xué)。

?

(3)語言限定體驗(yàn):語言在瞬間體驗(yàn)中起著決定性的作用,歷史性的養(yǎng)成人們感受的習(xí)性,如明月、流水;也橫向地限定了人們的體驗(yàn)?zāi)芰头绞剑缭律⑽逦丁?/p>

?

(4)語言本身也可以是美的形象。

?

(5)語言可以“修改”現(xiàn)實(shí):王蒙是一個(gè)經(jīng)歷了“反右”和十年“文化大革命”的作家,特殊的經(jīng)歷和遭遇使他對“語言”的負(fù)面功能有著特殊的認(rèn)識。他說,“語言文字可以反過來主宰我們,扼殺我們的創(chuàng)造性,扼殺我們活潑的生機(jī)”。

n

五、“概念恐懼”與語言的權(quán)利

存在主義哲學(xué)家基爾克郭爾提出“概念恐懼”認(rèn)為,“恐懼”和“畏懼”不同,前者是對沒有具體對象的恐懼。在十年“文化大革命”中,很多語言就是這樣造成一種“恐懼”,這些語言并沒有創(chuàng)造“實(shí)體”,比如“地富反壞右”、“牛鬼蛇神”等。“一個(gè)青年在街上走”,這說明了語言本身隱藏著權(quán)力,影響我們的認(rèn)識和思考。事實(shí)上,有“語言”的地方,就存在著權(quán)力的妥協(xié)、對立和斗爭,就存在著“扼殺”和對“扼殺”的反抗。

n

六、再談?wù)勚v演

講演稿也叫演說詞,是在較隆重的集會和會議上發(fā)表的講話文稿。可以用來交流思想、感情,表達(dá)主張、見解,具有宣傳、鼓動和教育作用。

演講是一種溝通。在古代希臘,演講被稱之為“誘動術(shù)”。這包含了三個(gè)意思:

(1)廣場性:利用話語修辭,調(diào)動公眾情緒的相互感染;

(2)單向性:含有表演性質(zhì)的獨(dú)白話語行為;

(3)

共謀性:演講是一種修辭性的“共謀”策略的實(shí)施。

七、王蒙講演的風(fēng)格:

?

外松內(nèi)緊

?

亦莊亦諧

?

取譬引喻

n

八、思考與討論

l

找出表現(xiàn)演講者機(jī)智的句子。

l

怎樣區(qū)分演講中的幽默與噱頭?

l

舉例說明演講者是怎樣不斷調(diào)動、活躍場內(nèi)氣氛的。

l

你同意演講者關(guān)于“語言陷阱”的觀點(diǎn)嗎?為什么?

第四篇:面試中要注意的語言陷阱與應(yīng)答技巧

就業(yè)面試經(jīng)典問題及最佳答案 工作動機(jī) 個(gè)人愿望篇

自考生就業(yè)面試經(jīng)典問題”見招拆招“

工作動機(jī)、個(gè)人愿望·

請給我們談?wù)勀阕约旱囊恍┣闆r·你是哪年出生的?你是哪所大學(xué)畢業(yè)的?問題:請給我們談?wù)勀阕约旱囊恍┣闆r

回答:簡要的描述你的相關(guān)工作經(jīng)歷以及你的一些特征,包括與人相處的能力和個(gè)人的性格特征。如果你一下子不能夠確定面試者到底需要什么樣的內(nèi)容,你可以這樣說:”有沒有什么您特別感興趣的范圍?“

點(diǎn)評:企業(yè)以此來判斷是否應(yīng)該聘用你。通過你的談?wù)摚梢钥闯瞿阆氲氖侨绾螢楣拘Я€是那些會影響工作的個(gè)人問題。當(dāng)然,還可以知道你的一些背景。

·請談一下你對公司的看法,為什么你想來工作?

問題:你是哪年出生的?你是哪所大學(xué)畢業(yè)的?等等

回答:我是XXXX年出生的。我是XX大學(xué)畢業(yè)的。

點(diǎn)評:這類問題至為關(guān)鍵的是要針對每個(gè)問題簡潔明了的回答,不可拖泥帶水,也 不必再加什么說明。完全不必再畫蛇添足的說”我屬X,今年XX歲“之類的話。至于專業(yè)等或許主考官接下來的問題就是針對此而言的,故而不必迫不及待和盤托出。

·你認(rèn)為對你來說現(xiàn)在找一份工作是不是不太容易?

問題:你認(rèn)為對你來說現(xiàn)在找一份工作是不是不太容易,或者你很需要這份工作?

回答:

1.是的。

2.我看不見得。

點(diǎn)評:

一般按1回答,一切便大功告成。

有些同學(xué)為了顯示自己的”不卑不亢“,強(qiáng)調(diào)個(gè)人尊嚴(yán),故按2回答。結(jié)果,用人單位打消了錄用該生的念頭,理由是:”此人比較傲“一句話,斷送了該生一次較好的就業(yè)機(jī)會。

·你是怎么應(yīng)聘到我們公司的?

問題:你是怎么應(yīng)聘到我們公司的?

回答:貴公司是國際上有名的汽車工業(yè)公司,雖然我學(xué)的專業(yè)不是汽車專業(yè),但我一直留意、關(guān)心貴公司的發(fā)展,特別是貴公司注重對員工的培訓(xùn),更讓我心動,另外象貴公司這樣大的企業(yè),我想是各種專業(yè)人才都需要的,便毅然前來應(yīng)聘。

點(diǎn)評:該畢業(yè)生的專業(yè)雖然不是該公司緊缺的專業(yè),但他分析了公司招聘職位的具體要求,認(rèn)為可以應(yīng)試該公司的某一種職位要求。(如管理、營銷、秘書),如食品工程專業(yè)的求職面遠(yuǎn)不只局限于食品的加工企業(yè),可延伸至飲品、酒類、保健品、調(diào)味品甚至酒樓等多個(gè)行業(yè)。都會有適合自己的職位。·請你談?wù)剬ξ覇挝坏目捶ā?/p>

問題:請你談?wù)剬ξ覇挝坏目捶?/p>

回答:我對貴單位還沒什么了解,故談不出看法

點(diǎn)評:象這樣的回答,一般面試不成功多,如你很想進(jìn)入該單位,就不妨實(shí)地去單位”偵察“一番,或收集有關(guān)的資料。如有一位畢業(yè)生,他有意去國家進(jìn)出口銀行工作,便通過朋友的關(guān)系弄到了一本進(jìn)出口銀行的基本業(yè)務(wù)材料,從而在面試中對答如流,贏得了招聘單位的賞識。并能以自身的優(yōu)勢來說明為何應(yīng)聘這工作,做到有的防矢,給主考官留下了深刻的印象。因此,收集資料,了解單位,可以幫助求職者認(rèn)清主要方向,更精確,更客觀地審視主聘單位,選擇適合自己發(fā)展的單位,避免走彎路。

你完全可以到大公司任職,你怎么想到我們小企業(yè)?

問題:以你的資歷條件,完全可以到大公司任職,你怎么想到我們小企業(yè)?回答:

1.哎,沒辦法,一時(shí)沒有應(yīng)聘到大企業(yè),況且,畢業(yè)時(shí)間又到了,否則只能回當(dāng)?shù)鼐蜆I(yè),因此先就業(yè)再說。

2.小企業(yè)有他自己的優(yōu)勢,在用人方面非常重視,自己雖然資歷條件尚可,我想,在你們這樣的企業(yè)更能發(fā)揮自己的作用。

點(diǎn)評:一個(gè)還未工作就想以后跳槽的員工,是無論如何不能指望他盡心盡力的干好工作的,因此,即使有此想法,也不能說出來,說不定工作后受到企業(yè)重用,本人的作用也發(fā)揮的特別好,而不想再走了呢?

·你為什么希望到我們公司工作?

問題:你為什么希望到我們公司工作?

回答:我覺得貴公司力量雄厚,領(lǐng)導(dǎo)得力,上下一心,適于一切有才干的人發(fā)展。

忌:”我是學(xué)電子的,我到這里才是專業(yè)對口。“看情況而定。

”我來這里上班離家近。“

”我喜歡你們這兒。“

”聽說你們公司月薪較高。“

點(diǎn)評:回答問題要從對方入題,引起對方好感,使對方感到你能尊重,關(guān)心公司的需要,愿為公司盡微薄之力。

·如果公司錄用你,你最希望在哪個(gè)部門工作?

問題:如果本公司錄用你,你最希望在哪個(gè)部門工作?

回答:

忌:”到哪個(gè)部門都行“

應(yīng):”本人希望 到XX部門,但也很樂意接受公司的其他安排。

點(diǎn)評:不要說得太隨意,太肯定。比較穩(wěn)妥的辦法是首先表明自己的志向和興趣,再表示服從安排。

·你愿意被外派工作嗎?你愿意經(jīng)常出差嗎?

問題:你愿意被外派工作嗎?你愿意經(jīng)常出差嗎?

回答:愿意,反正我無牽無掛,到哪兒工作都可以。

點(diǎn)評:這是主試者通過提問來透露他要找的是什么樣的人,此信息已經(jīng)很明白地告訴你,他所期待的回答是什么。對于此類問題應(yīng)聘者留意傾聽。從“話中之話”中找出應(yīng)試者實(shí)際需要的線索。

我怎樣相信對這個(gè)職位你是最好的人選呢?

問題:我怎樣相信對這個(gè)職位你是最好的人選呢?

回答:根據(jù)這個(gè)職位的性質(zhì)和我們剛才的談話,我推斷你需要的是工作積極的人,能夠設(shè)定目標(biāo),不懼怕挑戰(zhàn)的人。我就具有這些品質(zhì),讓我再告訴你一些我在校時(shí)的經(jīng)歷,它們能說明我確實(shí)是你所需要的最好的人選。

點(diǎn)評:設(shè)身處地替面試官想一想,考慮一下招聘者需要什么樣的人,你又在哪些方面符合他們的要求。根據(jù)要求,談出自己應(yīng)聘的優(yōu)勢。

·如果我能給你任何你想要的工作,你會選擇什么?

問題:如果我能給你任何你想要的工作,你會選擇什么?你真正想做的是什么工作?

回答:就是這份工作。

點(diǎn)評:你可能覺得這是個(gè)怪問題,事實(shí)上常有這樣的問題。這個(gè)問題是假設(shè)每個(gè)人都有未實(shí)現(xiàn)的夢想,都不能做他真正想做的事,亦即或多或少每個(gè)人都在妥協(xié)。若你真的談了你的夢想,而他只會為圓你夢想的夢,而不錄用你。因此,你確實(shí)要這份工作,那么答案只有一個(gè)。

·為什么你還沒有找到工作?

問題:為什么你還沒有找到工作?

回答:我正在謹(jǐn)慎選擇我的工作,本來我可以選擇別的工作的,可是那些工作和現(xiàn)在這一個(gè)不同,我實(shí)在看不出它們會對我的事業(yè)進(jìn)展有幫助。

點(diǎn)評:如果你真的拒絕了其他人的錄取,那是再好不過了,如果其他企業(yè)都沒有錄取你,哪也不一定有問題。別人不能只因?yàn)槟悻F(xiàn)在沒有工作,就斷定都沒有人錄取你,不要給人這樣的錯(cuò)覺。

你對我們公司有多少了解?

問題:你對我們公司有多少了解?

回答:

1.完全不了解。

2.因?yàn)閷F公司有關(guān)方面相當(dāng)有興趣,所以才來應(yīng)聘。

點(diǎn)評:若回答1.那就沒有必要再說下去了,但錄用的機(jī)會也就小了。最好的回答是2,這是公司想測試應(yīng)聘者對公司的興趣,關(guān)注程度,以后進(jìn)公司工作的意愿的問題,因此,最好要稍稍記住公司的簡介內(nèi)容和招聘人事廣告內(nèi)容。你對公司有何印象?

問題:你對公司有何印象?

回答:感覺很好,在其他公司沒有這樣的感受。

點(diǎn)評:或者說出面試當(dāng)天的印象就可以了,因?yàn)檫€沒有正式進(jìn)入公司上班,所以主試者也不會太過刁難。

·你談?wù)勥x擇這份工作的動機(jī)?

問題:你談?wù)勥x擇這份工作的動機(jī)?

回答:“這個(gè)職位剛好是我的專業(yè)對口,能把學(xué)的書本知識在實(shí)踐中更好地應(yīng)用。”

“我雖然學(xué)的專業(yè)與這職位有區(qū)別,但我對這方面的能力較強(qiáng),相信自己能干好這份工作。

點(diǎn)評:這是測試面試者對這份工作的理解程度及熱忱,并篩選因一時(shí)興起而來應(yīng)聘的人。

你家在外地,單位無住宿條件,你如何看待呢?

問題:你家在外地,我們單位無住宿,你如何看待呢?

回答:家在外地,貴單位無住宿條件,這些都不影響我來應(yīng)聘貴公司,住宿我可以自己解決,無須單位操心,我看重貴公司的發(fā)展前途。

點(diǎn)評:不要因?yàn)閭€(gè)人生活上的小問題,而錯(cuò)失良機(jī)。主試者也想看看你對困難的看法,自信心程度。

我們不限定固定職位,你認(rèn)為自己最適合做什么?

問題:我們不限定固定職位,你認(rèn)為自己最適合做什么?

回答:

忌:”公司安排我做什么就做什么!“太隨意。

”理想的職位就是有機(jī)會讓我一展專長,為公司的發(fā)展貢獻(xiàn)自己的學(xué)識。“太空。

應(yīng):我學(xué)的是XX專業(yè),我認(rèn)為XX職位比較適合我。

點(diǎn)評:主試者問你問題,就是想要一個(gè)明確的答案,且明確的回答給人以有思想、有主見、有活力的印象。象上面的回答,是犯了一個(gè)錯(cuò)誤,然而幾乎每個(gè)人都會犯同樣的錯(cuò)誤,他們總是說自己干什么都可以。因此,回答這樣的問題,干脆用自己的心里話表白,實(shí)事求是,至少讓主試者聽起來感到舒服些。你希望從事什么樣的工作?

問題:你希望從事什么樣的工作?

回答:根據(jù)貴公司的招聘職位,我認(rèn)為**職位可能比較適合我,有利于我的能力的發(fā)揮。當(dāng)然,其他有些職位也是可做的,人貴在學(xué)習(xí)。

點(diǎn)評:應(yīng)試者可以應(yīng)聘的職位作出大致的設(shè)想,讓主試人了解自己的抱負(fù)與努力方向。由于每個(gè)單位都有自己的人事政策,其工作安排未必能完全與求職者的愿望相一致,尤其對一個(gè)初出茅廬的大學(xué)生來說,從基層做起,從小事做起也是應(yīng)該的。但是,又不能隨便回答:”到哪里工作都可以。“這讓人覺得像在”乞討工作“,被人看輕。所以要掌握分寸。

你為什么要應(yīng)聘我們公司?

問題:你為什么要應(yīng)聘我們公司?

回答:看了貴公司的廣告及要求,感到自己比較符合公司的招聘條件,另外,對貴公司也有些了解,自己若能有幸成為貴公司的一員,是能有助于自己能力的發(fā)揮與發(fā)展的。

點(diǎn)評:這樣的回答,可顯示出自己積極進(jìn)取的態(tài)度。在談?wù)撚萌藛挝粫r(shí),態(tài)度要誠懇、謙和。不論大單位或小單位,都有其優(yōu)勝和劣勢,應(yīng)試者應(yīng)視其實(shí)際情況,提出自己的見解,不要牽強(qiáng)附會,如果一味往對方臉上貼金,反而會令人反感。

·你在以前實(shí)習(xí)的公司從事什么樣的工作?

問題:你在以前實(shí)習(xí)的公司從事什么樣的工作?

回答:在具體說明對工作的理解程度和熟悉度時(shí),回答要領(lǐng)有三個(gè)方面:擔(dān)任的工作內(nèi)容、職務(wù)、成績?nèi)?xiàng)。

點(diǎn)評:這個(gè)問題可以讓公司知道面試者是否符合所要招聘的職位,以前在其他公司的職位是否重要,來判斷應(yīng)聘者的發(fā)展可能。

你為何選擇應(yīng)聘我們公司?

問題:你為何選擇應(yīng)聘我們公司?

回答:我對貴公司有一定的了解,特別對公司的XX經(jīng)營理念,產(chǎn)品質(zhì)量及員工培訓(xùn)比較看好。

點(diǎn)評:為了表明應(yīng)聘原因及工作意愿,應(yīng)聘者在回答時(shí)最好要了解企業(yè)狀況,不要籠統(tǒng)回答因?yàn)樽约簩碛邪l(fā)展,更不要回答為了安定等答案。

·在公司想做什么樣的工作?

問題:在公司想做什么樣的工作?

回答:現(xiàn)在想在某工作方面沖刺,將來則希望能在某方面努力等。朝自己想要的目標(biāo)陳述即可。

點(diǎn)評:同時(shí)招聘很多職種的公司,最有可能問到這樣的問題,這是判斷應(yīng)聘者個(gè)人的能力傾向。面試者如果不論職種都回答”可以“的話,反而會讓人懷疑工作態(tài)度。如果這家公司只招聘一個(gè)職種,還是被問到這個(gè)問題時(shí),是為了確認(rèn)應(yīng)聘者有無猶豫,應(yīng)聘者只要清楚的敘述自己想做的事就可以了。

·你為何要跳槽?

問題:你為何要跳槽?

回答:雖然在前面公司工作挺順的,同事間合作也很愉快,但我感到貴公司更適合我的發(fā)展。

點(diǎn)評:公司根據(jù)你跳槽原因,意在了解你的就業(yè)動機(jī)。

·請問你有什么樣的工作觀?

問題:請問你有什么樣的工作觀?

回答:我認(rèn)為工作是為了實(shí)現(xiàn)自己的人生價(jià)值,發(fā)揮自己的最大潛能,解決自己的生活問題。

點(diǎn)評:此話是問工作在你的生活中意味著什么?為何而工作?從工作中得到了什么?幾年后想變成怎樣等。因此,別把它想得太復(fù)雜,可根據(jù)自己的具體情況回答。

·你是否可以接受加班?

問題:你是否可以接受加班?

回答:我愿意接受挑戰(zhàn)。在自己責(zé)任范圍內(nèi)的工作,不能算是加班。

點(diǎn)評:這是面試者針對應(yīng)聘者的工作熱忱而提的問題,因無理的加班不一定是好的。

你認(rèn)為這份工作最重要的是什么?

問題:你認(rèn)為這份工作最重要的是什么?

回答:最重要的是對自己的挑戰(zhàn)和提高。

點(diǎn)評:對工作要加上自己的看法。

第五篇:Chapter1語言的功能與陷阱

大學(xué)語文題庫

一.語言的功能與陷阱 大學(xué)語文主要培養(yǎng)的是(C)。?A、背誦 ?B、書寫 ?C、語感 ?D、文采

(往年考過)2.(鏡像問題)王蒙的(A)這部作品給使他被錯(cuò)劃為右派。?A、《組織部來了個(gè)年輕人》 ?B、《青春萬歲》 ?C、《春盡江南》 ?D、《中國天機(jī)》

王蒙寫作的新中國歷史上第一部校園小說是(C)。A、《組織部來了個(gè)年輕人》 B、《語言的功能和陷阱》 C、《青春萬歲》 D、《戀愛的季節(jié)》

王蒙的第一部作品是()。A A.青春萬歲

B.組織部來了個(gè)年輕人 C.班主任 D.青春之歌

3.《人論》是(D)的作品。?A、笛卡爾 ?B、黑格爾 ?C、笛卡爾 ?D、卡西爾

4.“幸福”一詞在中國的廣泛使用源于(C)國家的影響。?A、美國 ?B、德國 ?C、蘇聯(lián) ?D、日本

5.(鏡像問題)“春心莫共花爭發(fā),一寸相思一寸灰”是(C)的作品。?A、李白 ?B、李賀 ?C、李商隱 ?D、李隆基

“春心莫共花爭發(fā),一寸相思一寸灰”是李商隱的作品。(是)

(往年考過)6.“言不盡意”最早是(B)意識到的問題。?A、孔子 ?B、老子 ?C、孟子 ?D、屈原

下面(C)最早提出了言不盡意的觀點(diǎn)。A、王蒙 B、蘇軾 C、老子 D、孔子

(往年考過)7.紅色文學(xué)的主題是(C)。?A、愛情 ?B、青春 ?C、革命 ?D、農(nóng)村

8.(鏡像問題)“寫小說就是寫語言”是(B)的名言。?A、巴金 ?B、汪曾祺 ?C、郭沫若 ?D、矛盾

(往年考過)“想象一種語言,就是想象一種社會生活”是(C)的觀點(diǎn)。?A、康德 ?B、薩特

?C、維特根斯坦 ?D、尼采

(往年考過)“語言的局限就是我們?nèi)渴澜绲木窒蕖笔牵―)的觀點(diǎn)。?A、馬克思 ?B、費(fèi)爾巴哈 ?C、黑格爾 ?D、恩格斯

(D)提出了“如果說不清楚就說明沒有想清楚,如果寫不清楚就說明沒有說清楚”。A、王蒙 B、徐志摩 C、臧克家 D、聞一多

(A)曾說過寫小說就是寫語言。A、汪曾祺 B、王蒙 C、普羅普 D、林風(fēng)眠

“語言是思想的物質(zhì)的、直接的現(xiàn)實(shí)”是(D)的觀點(diǎn)。A、弗洛伊德 B、王蒙 C、郭沫若 D、恩格斯 9.(鏡像問題)辨認(rèn)色彩最強(qiáng)的是(B)人。?A、亞洲 ?B、歐洲 ?C、非洲 ?D、美洲

中國人辨別色彩的能力強(qiáng)于歐洲人,是因?yàn)闈h語中表示色彩的詞匯非常豐富。()我的答案:×

10.《團(tuán)結(jié)一切抗日力量,反對反共頑固派》是毛澤東的作品,這篇文章的語言是面向(B)群體。

?A、知識分子 ?B、農(nóng)民 ?C、工人 ?D、官方

(往年考過)11.小品《主角和配角》反應(yīng)了(C)時(shí)期兩種群體力量的博弈。?A、抗戰(zhàn) ?B、大躍進(jìn) ?C、改革開放 ?D、現(xiàn)代化建設(shè)

12.小品《主角和配角》體現(xiàn)了語言和(C)的關(guān)系。?A、思想 ?B、意義 ?C、權(quán)力 ?D、情感

13.蔣介石在大陸第一次作為正面形象出現(xiàn)的電視劇是(D)。?A、《闖關(guān)東》 ?B、《席卷大西南》 ?C、《亮劍》

?D、《長沙保衛(wèi)戰(zhàn)》

(往年考過)14.《熱血、辛勞、汗水和眼淚》是(D)的演講。?A、希特勒 ?B、斯大林 ?C、毛澤東 ?D、丘吉爾

《熱血、辛勞、汗水和眼淚》是()時(shí)期的演講。C ? A 法國大革命 ? B 一戰(zhàn) ? C 二戰(zhàn)

? D 解放戰(zhàn)爭

15.(D)真正帶來了長篇小說的繁榮。A、毛筆寫作 B、鋼筆寫作 C、沾筆寫作 D、電腦寫作

16.(B)因?qū)懽骶﹦ 渡臣忆骸繁徽袅擞遗傻拿弊印、王蒙 B、汪曾祺 C、丁玲 D、艾青

17.下面詩句中的月亮不代表思鄉(xiāng)之情的是(A)。A、月明星稀,烏鵲南飛 B、海上生明月,天涯共此時(shí) C、舉頭望明月,低頭思故鄉(xiāng) D、露從今夜白,月是故鄉(xiāng)明

18.王蒙總結(jié)了語言的三種功能,其中不包括(B)。A、交流功能 B、區(qū)別功能 C、推動思想功能 D、浪漫功能

19.《俠客行》中,只有不識字的小孩認(rèn)出了蝌蚪文,這體現(xiàn)了語言的(D)。A、言不盡意 B、言過其實(shí) C、可替代性

D、對思想的束縛

20.老一輩的人不懂瑪麗蘇、大叔控等詞的意思,體現(xiàn)了語言(D)。A、是交流工具

B、是表達(dá)感情的媒介 C、具有大眾性 D、具有時(shí)代性

21.(鏡像問題)藝術(shù)的真正魅力來源于(D)。A、覺悟 B、修養(yǎng) C、藝術(shù)技巧 D、語言技術(shù)

藝術(shù)的真正魅力來自于語言。我的答案:√

22.下面詞語被語言賦予時(shí)間流逝感受的是(B)。A、月亮 B、流水 C、桃花 D、梅花

23.下面不能體現(xiàn)時(shí)間是空間的隱喻的是(C)。A、一頓飯的功夫 B、從前

C、9點(diǎn)10分 D、自古以來

24.毛澤東發(fā)表文章團(tuán)結(jié)廣大群眾抗日時(shí)使用的語言是(C)。A、嚴(yán)肅正規(guī)的語言 B、冷靜的語言 C、市井化的語言 D、有邏輯的語言

25.下面不能體現(xiàn)語言中包含著權(quán)力的是(B)。A、青年特指男性

B、俄羅斯的敘事詩很長 C、小老婆 D、叫花子

26.紅色文學(xué)的主題是()。C A 愛情 B 青春 C 革命 D 農(nóng)村

27.美國“垮掉的一代”反抗的是(C)。A、政府 B、體制 C、父輩 D、戰(zhàn)爭

28.下面不能體現(xiàn)語言性別歧視的是(A)。A、女人不能罵人 B、默認(rèn)青年為男性

C、女人沒有按照正常人類的形式被命名 D、生男孩是可好,生女孩是也好

29.(鏡像問題)下面不屬于演講技術(shù)特點(diǎn)的是(D)。A、感染性 B、單向性 C、共謀性 D、冷靜性

演講需要從幾個(gè)方面增強(qiáng)感染力,其中不包括(C)。A、感情 B、獨(dú)語論斷 C、道理

D、自我打動

30.語言的權(quán)力效益體現(xiàn)在(D)。A、讓人覺得屈辱 B、讓人覺得憤怒 C、讓人覺得激動 D、以上都是

31.下面不能作為公共象征的是(B)A、熱血 B、電腦 C、長城 D、五星紅旗 我的答案:B(往年考過)32.《日喻說》是(A)的文章。A、蘇軾 B、韓愈 C、柳宗元 D、杜甫

33.黑格爾提出,人和動物最重要的區(qū)別是(D)。

A、勞動 B、思維 C、情感 D、語言

(往年考過)34..王蒙認(rèn)同語言工具論的提法。我的答案:×

35.王蒙提出人與動物最重要的區(qū)別是語言。()我的答案:×

(往年考過)36.《語言的功能與陷阱》是王蒙的一篇演講詞。我的答案:√

(往年考過)37.語言會激發(fā)思想,但也會扼殺人的創(chuàng)造力。我的答案:√

38.語言的“狗屎化效應(yīng)”指語言說得多了,語言往往就失去意義了。我的答案:√

(往年考過)39.語言規(guī)定了種種瞬間的體驗(yàn),甚至可以創(chuàng)造其本身不具有的意味。我的答案:√

(往年考過)40.在文學(xué)創(chuàng)作中,永遠(yuǎn)是內(nèi)容決定形式,形式服務(wù)于內(nèi)容。我的答案:×

(往年考過)41.語言不需要表達(dá)對象,本身就具有獨(dú)立的審美意義。我的答案:√

(往年考過)42.演講是一種公共話語行為。語言組織總是暗含著鼓動性和說服性。我的答案:√

43.在演講中,理性的說服比感情的感染更有效。我的答案:×

44.(鏡像問題)語言一定要符合現(xiàn)實(shí)生活,否則會產(chǎn)生消極的后果。我的答案:×

45語言來源于生活,不能脫離現(xiàn)實(shí)。()我的答案:×

(往年考過)46.語言本身隱藏著權(quán)力,本身可以影響我們的認(rèn)識和思考。我的答案:√

(往年考過)47.一般來講,作家的理論思考能力都很好。我的答案:×

48.語言有幫助思想、推動思想的功能。我的答案:√

(往年考過)49.語言一定要符合現(xiàn)實(shí)生活,否則會產(chǎn)生消極的后果。我的答案:×

50.想象一種語言,就是想象一種社會生活。我的答案:√

(往年考過)51.語言本身隱藏著權(quán)力,本身可以影響我們的認(rèn)識和思考。我的答案:√

52.作家寫作都是事先在腦子里構(gòu)思好情節(jié)大綱才開始寫的。()我的答案:×

53“不著一字,盡得風(fēng)流”體現(xiàn)了言不盡意的魅力。()我的答案:√

54.任何時(shí)候言過其實(shí)都會讓人反感,要盡量避免言過其實(shí)。()我的答案:×

55.不同的語言代表了不同的文化和社會地位。()我的答案:√

56.語言可以催生感情,即使是哈哈大笑也可能傳遞悲涼的情緒。我的答案:√

57.市場社會的代表語言就是官方語言。我的答案:×

58演講中主體置換的目的是改變聽眾的利益需求。()我的答案:×

59人們對客觀事物的感情很多時(shí)候會受到語言的影響。()我的答案:√

60虛假提問能夠增強(qiáng)說話的氣勢。()我的答案:√

61畫國畫的對墨色的分辨能力要強(qiáng)于畫油畫的 我的答案:√

63語言的形式可以決定語言的內(nèi)容。()我的答案:√

64蔣介石在大陸第一次作為正面人物出現(xiàn)的電視劇是《長沙保衛(wèi)戰(zhàn)》

我的答案:√

(往年考過)65從語言的使用上來講,使用反問句去詢問他人,通常是不禮貌的。我的答案:√

(往年考過)66在一個(gè)民主、自由的社會里,言論自由是社會得以正常運(yùn)作的基石,正是因?yàn)槌珜?dǎo)言論自由,因而也就排除了貴族或特權(quán)階層的存在可能,畢竟言論自由不等于言論特權(quán)。

我的答案:√

67王蒙曾在《語言的功能與陷阱中》舉了諸葛亮斬馬謖的故事,這個(gè)例子說明的是語言的哪個(gè)陷阱?言過其實(shí)

68王蒙曾在《語言的功能與陷阱中》舉了阿Q和徐志摩分別向吳媽示愛的例子,作者意圖要說明語言的哪種功能?修辭

69王蒙在《語言的功能與陷阱》中舉了“失空斬“的例子,用來說明語言的哪種問題?言過其實(shí)

70王蒙在《語言的功能與陷阱》中舉了”輪扁斫輪"的例子,用來說明語言的哪種陷阱?言不能達(dá)意

71王蒙在《語言的功能與陷阱》中曾提到李商隱的詩歌,是為了說明語言哪方面的問題?語言具有藝術(shù)和審美功能的問題 72列哪位作者曾經(jīng)擔(dān)任過中國的文化部長?王蒙

下載C語言缺陷與陷阱word格式文檔
下載C語言缺陷與陷阱.doc
將本文檔下載到自己電腦,方便修改和收藏,請勿使用迅雷等下載。
點(diǎn)此處下載文檔

文檔為doc格式


聲明:本文內(nèi)容由互聯(lián)網(wǎng)用戶自發(fā)貢獻(xiàn)自行上傳,本網(wǎng)站不擁有所有權(quán),未作人工編輯處理,也不承擔(dān)相關(guān)法律責(zé)任。如果您發(fā)現(xiàn)有涉嫌版權(quán)的內(nèi)容,歡迎發(fā)送郵件至:645879355@qq.com 進(jìn)行舉報(bào),并提供相關(guān)證據(jù),工作人員會在5個(gè)工作日內(nèi)聯(lián)系你,一經(jīng)查實(shí),本站將立刻刪除涉嫌侵權(quán)內(nèi)容。

相關(guān)范文推薦

    王蒙《語言的功能和陷阱》(模版)

    王蒙《語言的功能與陷阱》 語言對人來說是太重要了,可以說是人與非人之間的一個(gè)非常重大的區(qū)別,當(dāng)然我們首先倡導(dǎo)的學(xué)說是勞動創(chuàng)造了人,但是從一定意義上也可以說語言和勞動一......

    面試中你必須要知道的語言陷阱5篇

    面試中你必須要知道的語言陷阱如:“你經(jīng)歷太單純,而我們需要的是社會經(jīng)驗(yàn)豐富的人”,“你性格過于內(nèi)向,這恐怕與我們的職業(yè)不合適”,“我們需要名牌院校的畢業(yè)生,你并非畢業(yè)于名牌......

    事業(yè)單位考試:面試技巧之如何避開語言陷阱

    文章來自赤峰人事考試信息網(wǎng):http://chifeng.offcn.com 事業(yè)單位考試:面試技巧之如何避開語言陷阱事業(yè)單位:語言,是事業(yè)單位面試重要的內(nèi)容之一。有些參加事業(yè)單位考試的考生可......

    薪酬陷阱

    薪酬陷阱:求職業(yè)要小心啊 找工作不是難事,找到一份合適自己又喜歡的工作就是難上加難了,而找到一份自己喜歡而工資又高的簡直就比登天還難了,所以大家總是在不斷地尋找高工資的......

    合同陷阱

    新房首頁 > 資訊 > 買房知識 > 全文 合同:合同陷阱摘要:實(shí)例1:《××居商品房認(rèn)購書》中規(guī)定:“若乙方支付定金之日起十天內(nèi)未能依時(shí)簽署《商品房買賣合同》及交付首期房價(jià)款,則......

    醫(yī)療缺陷標(biāo)準(zhǔn)與管理辦法

    洪江市第一中醫(yī)醫(yī)院 醫(yī)療缺陷標(biāo)準(zhǔn)與管理辦法 醫(yī)療糾紛大多因醫(yī)療缺陷引起。因此加強(qiáng)醫(yī)療缺陷的管理就顯得尤為重要。根據(jù)國家醫(yī)療質(zhì)量管理標(biāo)準(zhǔn)及有關(guān)文件精神,結(jié)合我院實(shí)際情......

    作文:缺陷與圓滿(教案)

    作文:缺陷與圓滿(教案) 【作文材料】 殘疾人舞蹈家邰麗華因領(lǐng)舞“千手觀音”而家喻戶曉,當(dāng)有人問她:“你會不會覺得老天不公平,沒有給你一個(gè)健全的身體,讓你聽不到這個(gè)世界?”邰麗華......

    公務(wù)員法的缺陷與完善

    《公務(wù)員法》的修改與完善K060841037李玉果摘要:公務(wù)員法是一個(gè)由憲法、國務(wù)院組織法、地方組織法、法官法、檢察官法、警察法、監(jiān)察法等國務(wù)院有關(guān)公務(wù)員管理的行政法規(guī),以......

主站蜘蛛池模板: 草草久久久无码国产专区| 一区二区传媒有限公司| 国产精品久久久久av| 成年无码av片在线| 亚洲欧美成人a∨观看| 韩国午夜理伦三级在线观看| 国产精品免费精品自在线观看| 亚洲成无码人在线观看| 免费人成自慰网站| 久久99精品九九九久久婷婷| 久久久久亚洲AV成人无码电影| 国产精品亚洲αv天堂无码| 久久久久日本精品人妻aⅴ毛片| 性色av一区二区三区夜夜嗨| 国产内射999视频一区| 亚洲成在人线av| 亚洲熟妇av一区二区三区漫画| 成人性生交大片免费| 久久亚洲人成综合网| 热99re久久精品国产首页免费| 久久久久久无码日韩欧美| 国产乱妇无乱码大黄aa片| 国产免费人成在线视频| 亚洲a综合一区二区三区| 国产精品va在线观看手机版hd| 久久大香伊蕉在人线国产h| 国产精品午夜不卡片在线| 97国产在线看片免费人成视频| 最新精品国偷自产在线下载| 国产精品青草久久福利不卡| 亚洲精品自在在线观看| 熟妇人妻无乱码中文字幕真矢织江| 久久人人玩人妻潮喷内射人人| 久久精品国产72国产精| 搡老熟女老女人一区二区| 国产人妻人伦精品1国产盗摄| 国产精品夜色一区二区三区| 真人一对一免费视频| 亚洲精品97久久中文字幕无码| 又爽又黄又无遮挡的视频| 国产明星女精品视频网站|