A. C語言的題目 在線等 謝謝~
這個表達式的求值順序為從右到左:
首先計算a/a的值為1,然後計算下一部分賦值語句,也就是a+=1,a的值變成5+1也就是6;下一步是a*=6,a自乘一次變成36;接下來計算a-=36,於是a=a-36,結果為0。
測試如圖,環境為WindowsXPSP3+MinGW(gcc4.3),以及ubuntu9.04(Linux2.6)gcc4.3
實際上如圖所示,gcc在-Wall開關下會提示a的這種賦值可能是未定義的行為,因而可能不同的編譯器實現會有不同的結果。也就是說,盡管ANSIC99標准中規定了表達式求值的順序是從右到左,但是並沒有規定在一個表達式中一個變數出現多次時,這個變數的值在計算過程中是不是會變。
上面給出的gcc的實現方式是對a隨時求值並賦值,然而另一個編譯器可能會這樣做:(首先咱們通俗的說一下^_^)
我們記住了a的值是5,然後開始從右到左計算:a/a值是1;接下來計算a+=1,根據我們的記憶,a的值是5,於是這部分的值就是6;同樣,計算a*=6時,根據我們的記憶,a的值是5,所以結果變成30;然後a-=30,便計算出5-30也就是-25的結果。
這一種理解方式可能跟我們看到的表達式不同,畢竟我們見到的表達式中是隨時賦值,而編譯器理解成a-=(a*(a+(a/a)))並不是我們想說的計算,——正因為這個原因,大部分編譯器實現時都採用的是第一種理解方式,也就是說,盡管C99中沒有明確規定,但是大家已經比較統一的採用了第一種也是比較自然的理解方式來編譯這段代碼。
存在第二種理解方式的原因在於,我們的變數是在內存中的,然而參與計算時大部分數據位於CPU的寄存器中。因而,如果我以這樣一種方式去實現運算和優化,即將我們認為經常用到的數據存儲在寄存器中,以便提高速度,就會變成這種情況。可能的運行過程如下:
編譯器發現a在語句中多次出現,於是將a的值直接mov到某個寄存器中,比如EBX,然後開始計算:a/a也就是EBX/EBX(甚至這句沒有而直接就是1),將結果1放在EAX中,然後下一步把EBX加到EAX上,就是那個a+=什麼什麼,然後再把EBX乘到EAX上,結果仍在EAX,最後EBX-EAX賦值給EAX,再將EAXmov到變數a,就得到了-25這樣的結果。
當然,上面僅僅是通過分析得到的可能的運行過程,鑒於大部分編譯器都不是這樣實現的,而且我這里只有gcc環境,沒有辦法進行測試,因而我們只能說可能存在,但沒有實例。
另外需要提醒樓主,所有牽扯到「未定義」(Undefined)行為的語句都是應該在編程中避免的,因為這些語句將依賴於您特定的編譯環境。像上面的語句,如果我想要說明白,並且避免未定義行為,就將該拆開的賦值拆開成多個語句,寫成這樣:
inta=5;
a+=a/a;
a*=a;
a-=a;
也就是盡量保持語句的簡單,一個語句中最多一個賦值。(KISS原則)
呵呵,沒想到我速度不夠快,樓主又增加了幾個題目。看到這些我才想起來,這應該是某些測試題上面的東西了吧?很多測試題甚至包括二級考試的題目都會考察一些與編譯器實現和特定環境相關的問題,而如果你不是要學習編譯原理這種課程,那麼這樣的考察是沒有任何意義的。很多這樣的練習題都是,不會對個人的編程能力有任何提升,反倒使得大家經常鑽入「未定義」的陷阱出不來。個人觀點是,如果想學好編程,自己動手實踐是最好的途徑。很多東西可以通過實踐去學習,比如您編程發現i++沒問題但是1++會報錯,於是您就知道了哦原來自增運算只能施加於左值(l-value)……當然一些基礎的內容還是必要的。實際上後面這幾個題目都很基礎,這樣的問題應該在學習C語言的前期就能夠自己解答的。
廢話說的不少,本來我看到第一個題目後只是想說明「未定義」行為的一些東西,所以就稀里嘩啦寫了這么多。但現在看看您下面的問題我覺得您可能只是想要答案而已,——於是我就順便把其他題目也答了吧,雖然我的建議是您最好自己去發現,通過教材、實踐。
第一個:未定義行為,大多數情況下會是0,具體原因前面說的比較清楚了。
第二個:再糾正一個觀點,就是這種不確定的問題並不是機器或者系統的問題,而在於編譯器的實現。比如我在32位機器上裝32位系統,而運行16位的TC,或者64位機器裝32位系統,運行16位程序TC,甚至64位機器裝64位系統,運行16位TC,最終由TC編譯出來的程序一定是被16位TC限定的只能使用寄存器中的AH,AL,AX,BX...而不能使用EAX,EBX,更不用提RAX(當然RAX們只有64位系統和64為程序才用得到),因而設計編譯器時只能自然使用AX大小的變數,也就是16位,於是TC中的int長度為16比特或者說兩個位元組。我想說明的是這些不確定的問題都是編譯器實現時造成的,跟系統無關。
好的言歸正傳,題目要求「值」所佔的位元組數,語句就是sizeof(a+4.5),然後您可以通過printf將這個結果輸出,當然這個結果在大多數32位編譯器上應該是8,因為a是整數,但是4.5是雙精度浮點數,運算將默認對整數進行類型轉換,變成同樣的double型後和4.5進行運算。得到的結果當然也是double型,——而幾乎所有32位編譯器實現中,double型佔8個位元組。
這里不得不提到的是C99標準的一個可能會造成眾多C程序員疑惑的地方,就是浮點數在程序中默認是雙精度的,除非強加f標志表示float型。於是,我們可以測試,如果在前面的語句中將4.5這個數字後面加上f標志成為4.5f,那麼結果可能是不一樣的。事實上,大部分編譯器將float規定為4位元組,這樣經過類型提升後,4位元組的int變成4位元組的float,和4位元組的float運算,得到的仍然是4位元組的float,於是就會得到結果為4。
好的我們繼續,第三題是基礎知識,很明顯的C語言中的運算符有左結合與右結合之分,因而A是錯誤的,故選A。這個就不多說了。
第四題:關於自增和自減,我們從這個運算符的名字就能夠看出,它們是針對一個東西的,而且這個東西要可以改變才行。專業一點兒就是,自增和自減運算僅施加於左值,所謂左值通俗講就是可以放在等號左邊的東西(比如變數,但不是全部變數都可以)另外有一種理解是可以在數據段定位(Locate)內存地址的東西,詳細點就是可以被賦值的變數,非「只讀變數」。或者就是我們通常所說的變數。(注意變數可以用const進行只讀修飾。)
給出答案:單目運算符,只能作用在左值上(或者可以說變數,不過不確切),不能作用在右值(兩個空就可以填成常量和表達式,但是注意這也是不確切的)。
這個問題很難給出一個確切的答案,——這也是好多這種題目的共同特徵,個人感覺沒有必要確切到什麼地步,只要能理解就行。而C語言中很多標準的地方都是有爭議的,比如左值右值的問題。這些在實際編程中真的都不造成影響,但是有些人就願意把帶有這種色彩的不確定性用來考試……
B. c++問題,幫幫忙。。。
額,int a 先在內存中給變數a申請空間 然後a=12,給這個變數賦值,也就是你用到a的時候,編譯器會查看在a的內存地址,看看他是否被賦值了
C. C語言 編譯器 判斷變數作用域的方法
你要注意一點,函數內的 每個{}對的區域都是有一個上級{}的, 確定了這個關系後,在一個子{}內部,判斷變數是否定義過的過程就是, 本{}中有沒有定義,其上級{}中有沒有定義,再上級有沒有,如果必要一直判斷到全局變數。
這里有一點還要注意, 注意變數定義和{}塊的先後關系。
D. 問一下各位,c語言編譯器是如何處理變數名的呢
編譯器編譯到int a;時就在內存中開辟一個兩位元組的內存空間,並且命名為a
E. C語言問題
雖然當變數沒有初始化的時候,變數的值是一個無法確定的垃圾值,但不能認為是把垃圾值賦值給了變數。
這里涉及到一些內存和編譯器的知識。當生命變數A的時候,編譯器要開辟一塊內存空間來存儲該變數,比如地址為B。因為內存中的數據是無法預知的,所以分配給A變數的內存地址中的數據是什麼也是無法預知的。如果不初始化,則地址B中的數據是隨機的,有可能指向其它的地方。因此,從表面上看就相當於把一個隨機的垃圾值賦值給了A。
想在好多語言,比如OC,在使用對象的時候,都會用[[object alloc] init]。從而很明確的表達出現申請空間,然後初始化空間的概念。
F. c語言的變數名
變數名不佔空間 變數:用來標識(identify)一塊內存區域,這塊區域的值一般是可以更改的,這就是它「變」的由來,但是我們可以通過使用如const等一些修飾符號來限定這一內存區域的操作特性(characteristic),即變數的操作特性。用const修飾的使變數不能更改的就和常量一樣的變數叫做常變數。 變數名:是一個標識符(identifier),用來指代一塊內存區域,即變數,使用變數使我們操作內存以區域(area),以塊(block)為單位,提高了方便性。 你的機器代碼中,是不會出現變數名的;變數名是給我們程序員操作內存來使用的。 想想在匯編年代,沒有變數名,我們操作內存,都是用地址來直接操作的,還要控制區域大小;當然匯編語言已經有了簡單的變數。 對於編譯器,它會搜集我們的變數名,比如我們定義了一個全局的int a;那麼編譯器都為我們做了什麼呢? 它會為程序預留4個位元組的空間(假設在32位平台),並把我們的變數名「a」保存進符號表,並用這個符號表的索引對應實際的空間。 如果下面出現b = a;那麼它就會根據符號表找到變數的真正的物理位置,取得它的值,賦給b。 這是寫編譯器需要做的,我們需要建立符號表。 但是實際在匯編層次上,操作的都是地址而已,不存在任何名稱了。
滿意請採納
G. 編譯系統(如:VC++6.0)編譯時對變數和變數地址如何處理
int a =5;//定義一個變數,並初始化
int *p //定義一指針變數
p = &a; //把a的地址賦給指針變數p
int &a = b;//a是b的引用。也就是b的一個別名。只要改變a的值,b的值也就改變了
H. c語言 指針
內存的基本單位是存儲單元
一個存儲單元佔8bit,也就是1Byte
每一個存儲單元都一個對應的地址
所謂地址,簡單的將就是一個編號,可以理解為每個人的身份證號碼一樣
指針變數p和變數a的內存是在棧上創建的
兩變數都佔4Byte,即4個連續的存儲單元
&p是指針p的首地址,也就是第一個存儲單元的編號(地址)
&a是變數a的首地址,也就是第一個存儲單元的編號(地址)
p = &a;的意思是將變數a的首地址賦給變數p,也可以說p保存了變數a的首地址
現在p和&a是一樣的地址,那麼相同的地址里所存放的內容自然也就一樣
用*p或a就可以取該地址里所存放的內容
所以*p和a的內容是一樣的
沒有分配地址一說,只有分配內存,分配存儲單元
地址,他的全稱叫物理地址或邏輯地址,是由段基址和偏移量構成,由地址累加器合成物理地址,也就是說每一個存儲單元都有一個對應的物理地址,他是規定好的,不存在分配,只有定址一說
所謂定址,就是指你所分配的內存的首地址,要看你的內存在什麼上創建?(C語言有三種內存創建方式)是該內存的那一段?才能給你相應的首地址,也就是這塊內存,這是由編譯器決定的,不是你決定的
如果你有興趣可以去學微機原理和編譯原理,比較復雜,牽扯硬體和匯編,還有數電,它會讓你更了解這些東西的
以你現在這個基礎,只要記住我上面寫的那些話就可以了,分清內存,存儲單元,地址之間的關系就夠了,其他的交給編譯器去做,不需要你太多了解,慢慢去學,不可能一口是個胖子,這是一個過程
I. 高分救助:幫我答幾道C語言題
轉貼的,網路可是個好東東^_^
1.1 這道題主要考察#的功能,S是一個表達式。TRACE()的作用就是在DEBUG狀態下,計算表達式S的值之前先列印S。
1.2 #error用於向編譯器報錯,並輸出它後面帶的錯誤信息。例如:
#ifndef SOMETHING
#error SOMETHING not defined!
#endif
如果在這段代碼之前未定義過SOMETHING,則在編譯時出錯,並給出"SOMETHING not defined!"的錯誤信息。
作者: 增壓Q因斯坦 2007-1-12 06:20 回復此發言
--------------------------------------------------------------------------------
2 偶素新來的,請吧主多多關照
1.3 #define NELEMENTS(array) (sizeof(array) / sizeof((array)[0]))
1.4 #define OFFSET(structure, member) ((int) &(((structure *)0)->member))
2 (a) An integer:int a;
(b) A pointer to an integer:int *a;
&; A pointer to a pointer to an integer:int **a;
(d) An array of 10 integers:int a[10];
(e) An array of 10 pointers to integers:int *a[10];
(f) A pointer to an array of 10 integers:int (*a)[10];
(g) A pointer to a <I>function</I> that takes an integer as an argument and returns an integer:int (*a)(int);
(h) An array of 10 pointers to <I>function</I>s that take an integer argument and return an integer:int (*a[10])(int);
3 char (*(*x())[])();
這道題來自"The C Programming Language"中的一個例子。
首先,確定標識符:x
x是一個函數,沒有參數:x()
返回值是一個指針:*x()
這個指針指向一個數組:(*x())[]
數組中的每個元素是指針:*(*x())[]
指向一個不帶參數的函數:(*(*x())[])()
函數的返回值是char:char (*(*x())[])()
這里,要知道*、()和[]的優先順序。
4 這個定義有點怪,它的意思是:jmp_buf這種類型是一個數組,只有一個元素,元素類型為struct{...}。數組名作為函數參數時,應該是傳遞地址/指針。
5 在編譯源文件時,C編譯器和C++編譯器都會對符號(函數或變數)名作某些修正,但兩者採用的修正方法不同,所以兩者生成的目標文件不能互相鏈接。在C+ +中使用extern "C"可以讓C++符號獲得C鏈接特性。由於C++編譯器會自動定義__cplusplus宏,所以在C語言頭文件中採用這種結構可以保證無論使用何種編譯器,生成的目標文件都具有C鏈接特性,能夠與標准C編譯器所生成的目標文件相鏈接。
6 (1)用於全局變數:外部靜態變數,只能在本源文件中被引用,不能被其它源文件所引用。
(2)用於局部變數:局部靜態變數,在函數返回後存儲單元不釋放;下一次調用該函數時,該變數為上次函數返回時的值。
(3)用於函數:內部函數,只能被本源文件中的函數所調用,不能被其它源文件調用。
7.1 const關鍵字在C語言中用於聲明"常變數",其值不可修改,但具有確定的數據類型。C編譯器總是為其分配相應的存儲單元。
在C++中,const關鍵字用於聲明常量,C++編譯器視具體情況決定是為其分配存儲單元還是僅將其作為編譯期間的常量。
7.2 const int a1; a1是整型常量。
int const a2; a2是整型常量。等同於const int a2;
const int *a3; a3是指針(a3是可變的),指向一個整型常量。等同於int const *a3;
int * const a4; a4是常量指針(a4不可變),指向一個整型變數。
int const * const a5; a5是常量指針(a5不可變),指向一個整型常量。等同於const int * const a5;
8.1 volatile關鍵字用於聲明內存映射的易失型變數,這類變數的值隨時可能由於某種編譯器所不知道的原因(例如,外部設備對其寫入)所改變,所以編譯器在進行代碼優化時不能對其做任何的假設和依賴。
8.2 volatile可以和const一起使用,不過很少見。
const關鍵字的意思是限制編程者自己不能修改變數的值;兩者並不矛盾。
例如一個內存映射的、只讀的硬體寄存器,假設它的地址是p,則可以這樣聲明:volatile const UINT32 *p;
9 sizeof(pmsg) = 指針變數的長度
sizeof(msg) = 2 (字元數組的長度)
sizeof("A") = 2 (字元串的長度)
sizeof(ch) = 1 (字元變數的長度)
sizeof(『A』) = 整型變數的長度 (在C語言中,字元常量的數據類型實際上是int;在C++中,它的數據類型是char,從而原式等於1)
sizeof(param) = 指針變數的長度 (數組名作參數時,傳遞的是數組的起始地址)
10 這種寫法是和編譯器&操作系統相關的,所以不應當這樣寫。在WIN2K+VC環境下debug程序時會出現異常。
作者: 增壓Q因斯坦 2007-1-12 06:20 回復此發言
--------------------------------------------------------------------------------
3 偶素新來的,請吧主多多關照
不過這樣寫,編譯器不會報錯。按道理,"hello..."的類型是const char [N],它是不能賦值給char *的,
因為會丟失常量屬性。但在const關鍵字成為C標准之前,大家都這樣寫程序,所以char *pmsg = "hello..."
這種寫法被給予特別豁免,即使在C++中也是如此,在"The C++ Programming Language"的附錄里對此有討論。
"hello, world!"是字元串常量(string literal),它的類型是const char [N],N為字元串的長度(包括結尾的0)。
"The C Programming Language"指出,寫字元串常量的結果是未定義的(undefined)。所以在有些平台(操作系統+編譯器)
上程序不會出錯,而在其它平台上程序出現異常。
GNU手冊里這樣說:
Writing into string constants is a very bad idea; "constants" should be constant.
不過在GNU中它提供另外的選擇:使用-fwritable-strings進行編譯就可以。
那麼,為什麼不允許修改字元串常量呢(它不也在內存中嗎)?
這可能和另外一個特點有關,即重復字元串的合並。現在的編譯器應該會主動幫助我們合並程序中相同的字元串常量
以節省內存。如果string literal可寫,就會出現問題。例如:
void foo()
{
printf("%s\n", "how are you?");
}
void bar()
{
char *p = "how are you?";
strcpy(p, "WHO ARE YOU?");
}
調用foo()當然會列印"how are you"。但如果編譯器合並字元串,那麼先調用bar(),再調用foo(),foo()列印的就是
"WHO ARE YOU?"。這當然不是我們想要的結果。
另外一方面,這樣寫也有問題(確實有人這么寫):
if (func() == "something")
...
func()是:
char *func()
{
...
return "something";
}
這就假設編譯器一定會幫我們合並字元串,然而那也不一定。
11 輸出"> 6"。
混合運算時的數據類型轉換次序:int --> unsigned --> long --> double。
另外,char和short必定轉換為int,float必定轉換為double。
12 p = (int *)((unsigned int)a + 1);
代碼的意圖是想使p指向數組的第二個元素,但通常的寫法是:p=a+1。這里存在這樣的問題:a是個常量地址,
a+1指向下一個數組元素,而((unsigned int)a + 1)指向下一個內存地址。如果地址是位元組計數的,則p指向的
是數組第一個元素的第二個位元組。還有一個效果就是:在RISC上該printf語句會出異常,因為不允許非對齊訪問
(mis-aligned access)。對齊訪問就是訪問2位元組變數的地址要能被2整除,4位元組變數的地址要能被4整除,etc。
13 這些函數到處都查得到,就不用做了吧.
【2 數據聲明和定義】
給定以下類型的變數a的定義式:
a) An integer int i = 0;
b) A pointer to an integer int *p = NULL;
c) A pointer to a pointer to an integer int **pp = NULL;
d) An array of 10 integers int a[10];
e) An array of 10 pointers to integers int *pa[10];
f) A pointer to an array of 10 integers int *ap = a;
g) A pointer to a <I>function</I> that takes an integer as an argument and returns an integer int ( *fp)( int )=NULL;
h) An array of ten pointers to <I>function</I>s that take an integer argument and return an integer typedef int( *fptype )( int );
fptype fpa[10];
【3 復雜類型(1)】
有如下表達式:
char (*(*x())[])();
請用文字描述x是什麼?
回答:聲明x是一個函數指針數組,函數的返回值類型是字元指針,參數為空。
空指針同志在《解釋一下 typedef int (*fn_ptr)()》這篇帖子里說過
c專家編程中理解c語言聲明的優先順序原則,如下:
1) 聲明從它的名字開始讀取,然後按照優先順序順序依次讀取
2) 優先順序地從高到低的順序是
a)聲明中括弧被括起來的部分
b)後綴操作符:
括弧()表示這是一個函數,方括弧表示這是一個數組
作者: 增壓Q因斯坦 2007-1-12 06:20 回復此發言
--------------------------------------------------------------------------------
4 偶素新來的,請吧主多多關照
c)前綴操作符,*表示這是一個指針
3)如果有const或volatile後面緊跟類型說明符如int等,那麼它作用於類型說明符,在其它情況下,const等作用於緊鄰的指針星號。
還真派上用場了,特致謝,不知對不對。
【4 復雜類型(2)】
jmp_buf的定義:
typedef struct _jmp_buf
{
REG_SET reg;
int extra[3];
} jmp_buf[1];
setjmp函數的原型:
extern int setjmp (jmp_buf __env);
問:調用setjmp時傳遞__env的內容,還是傳遞指針?
答:傳遞指針。由jmp_buf 的定義來看,它是一個數組類型,並且只含一個元素。由此_env是一個數組,在函數的形參中,任何形式的數組都是指針等價的,傳遞參數時數組名將轉化為相應的指針類型。實際上,在所有表達式中都是如此。
為什麼要把jmp_buf定義為數組呢?因為setjmp需要改變__env的內容,這必須要用指針才行。但為什麼不直接把參數定義為指針類型而是用這樣一種奇特的方法,我也不清楚。
【5 頭文件】
問:為什麼標准頭文件都有類似以下的結構?
#ifndef __INCvxWorksh
#define __INCvxWorksh
#ifdef __cplusplus
extern "C" {
#endif
/*...*/
#ifdef __cplusplus
}
#endif
#endif /* __INCvxWorksh */
這個不太會,但試一試。
答:為了使c++兼容c,在c++中也能調用c的標准庫。如果定義了__cplusplus測試宏,則編譯時進行extern "C"聲明。
#ifndef __INCvxWorksh
#define __INCvxWorksh
這倆是什麼不清楚。
【6 static關鍵字】
請說出static關鍵字的3種用處:
(1)用於全局變數;
(2)用於局部變數;
(3)用於函數。
/* file.c */
static int a; /* a只能在file.c中被引用*/
int b;
static int fn() /*fn只能在file.c中被引用*/
{
static int x; /* x的生存周期和程序的生存周期一樣 */
int y;
}
答:見注釋。
7.1 const關鍵字的意義是什麼?
7.2 解釋以下的變數定義:
const int a1; //a1為整型常量
int const a2; //同上
const int *a3; //a3整型指針常量,a3本身是個常量
int * const a4; //a4是整型常量指針,a4所指內容是常量
int const * const a5; //a5是整型常量指針常量,a5本身和a5所指內容都是常量
暈了
【9 sizeof()】
有以下定義:
char *pmsg = "A";
char msg[] = "A";
char ch = 'A';
答:
sizeof(pmsg) = 4 //32位編譯器
sizeof(msg) = 2 //字元'A'和'\0'
sizeof(「A」) = 2 //同上
sizeof(ch) = 1
sizeof(『A』) = 1 (在C++中等於多少?)
void f(char param[100])
{
// sizeof(param) = 4 //32位編譯器
}
評論Feed
J. 11.變數定義並初始化正確的是( )
A中 定義了變數a設定初值I,只有假設變數I是已經定義過的變數語句才有效,其次定義了指針p,整型指針應該用於保存整型變數的地址,將變數a的內容送入指針,語法不錯,但邏輯意義是錯的。
B中,a是整型變數,*a的計算方式錯誤,*應用用於對指針的操作
C中,定義的p是整型變數,不應該用於保存變數的地址
只有D是完全正確的。