编译器构造c语言描述pdf,关于编译器构造:为什么每次都要在C中指定数据类型?...
從下面的代碼片段中可以看到,我聲明了一個char變量和一個int變量。 編譯代碼時,它必須標識變量str和i的數據類型。
為什么在掃描變量期間需要通過將%s或%d指定為scanf來再次告訴它是字符串還是整數變量? 聲明變量時,編譯器還不夠成熟,無法識別嗎?
#include
int main ()
{
char str [80];
int i;
printf ("Enter your family name:");
scanf ("%s",str);
printf ("Enter your age:");
scanf ("%d",&i);
return 0;
}
%x,%d,%s都是"說明" printf()如何顯示數據的格式說明符;即是將位流顯示為十六進制數字還是十進制整數或ASCII表示形式。數據就是數據就是數據。 :-)程序員(使用printf)可以隨意解釋它。
請參見Yu Haos的答案...問題是printf和scanf是varargs函數,這意味著它們的參數沒有靜態類型。 C是弱類型的……函數沒有可以檢查的運行時類型信息。格式用于此目的。
我對格式說明符的回答如何?并且您認為@xvp先生建議使用scanf()不一定要使用stdio.h嗎?
誰說格式始終是編譯器可以分析的像"%s"這樣的字符串文字?它可以是具有計算值的字符串。編譯器無法在編譯時分析它們。
編譯器的成熟與否是否與語言功能有關,任何人都難以回答關于您的編譯器的問題,而您還沒有說是哪個。
盡管C語言的局限性以及您可以在運行時創建格式字符串的事實意味著對于printf,scanf和朋友,您將始終需要再次指定類型,如果格式字符串是經過硬編碼的,則有些編譯器至少會為您驗證類型是否匹配:gcc.gnu.org/onlinedocs/gcc/
@TheCodeArtist如果這足以關閉程序員的C語言,我們還是不希望他們。
盡管這一點是正確的,但沒有真正的原因使編譯器看不到"%?"。 (其中%?是編譯器擴展),然后使用typeof(next_arg_in_list)來猜測是什么?應該被替換為,并帶有使用" 0x"之類的上下文來指定十六進制等的獎勵。
在許多方面,這一評論之上的第二條評論可能是我見過的最糟糕的評論...
@technosaurus首先,該建議是一種提供運行時類型信息的方法,與OP完全不同,后者將完全消除format參數。其次,有一個真正的原因:C語言標準不支持任何此類東西。也許有一天會。
因為像scanf和printf這樣的變量參數函數沒有可移植的方式來知道變量參數的類型,甚至沒有傳遞多少個參數。
請參見C常見問題解答:如何發現函數實際使用了多少個參數?
這就是為什么必須至少有一個固定的自變量來確定變量自變量的數量,也許是類型的原因。這個參數(標準稱為parmN,請參見C11(ISO / IEC 9899:201x)§7.16變量參數)起著特殊的作用,并將被傳遞給宏va_start。換句話說,您無法在標準C語言中使用具有以下原型的函數:
void foo(...);
+1是這里唯一能識別出問題所在的答案...缺少可變參數的靜態類型。
另外,如上面的注釋中所述,在var類型和printf格式"類型"之間沒有一一對應的關系。
@MarkHu是的,但這更像是結果,而不是原因。無論如何,請考慮一下,為什么printf的格式說明符使用%f輸出double,float呢?
在我上面的評論之后添加的第二段是非常不幸的,完全是錯誤的。僅在沒有添加其他語言功能的情況下,沒有其他方法可以在基于堆棧的實現中指定參數的起始地址,才需要第一個參數。確定參數類型的其他方法有很多,包括全局變量,并且沒有特殊的原因必須將類型信息放在第一個參數中……例如,在開始未知類型的參數之前,可以有5個已知類型的參數,并且您仍然必須對所有這些都使用va_arg。
@JimBalter我沒有在任何地方說第一個論點,對嗎?根據定義,parmN是...之前最右邊的參數,它不一定是第一個參數。在標準C中是必需的。
@YuHao是的,我討厭第一個參數,但是其余的都是正確的……需要parmN才能將地址??放入堆棧;它與如何確定類型無關,這取決于函數……而類型肯定不必從parmN派生。當然,它是標準C的"必需",因為它是標準的一部分...我在X3J11上,并且確切地知道為什么...這是現有實現中現有varargs機制的標準化,并由C編譯器供應商認可誰主導了委員會。
@JimBalter我的意思是,即使您使用其他方法確定類型信息,標準C變量自變量函數也必須在語法上具有parmN自變量。
您寫道:"這是原因……確定數量,也許是類型……"。那根本不是原因。您無法通過parmN判斷參數的數量。例如,對于某些函數,最后一個參數為NULL,否則為結束指示符。
最后,第二段與問題無關……第一段對此進行了說明。
編譯器無法提供必要信息的原因很簡單,因為此處未涉及編譯器。函數的原型未指定類型,因為這些函數具有變量類型。因此,實際的數據類型不是在編譯時確定的,而是在運行時確定的。
然后,該函數從堆棧中獲取一個參數,在另一個參數之后。這些值沒有任何關聯的類型信息,因此,函數唯一的方法就是通過使用調用方提供的信息(格式字符串)來知道如何解釋數據。
函數本身不知道傳入的數據類型,也不知道傳遞的參數數量,因此printf不能自行決定。
在C ++中,可以使用運算符重載,但這是一種完全不同的機制。因為在這里編譯器會根據數據類型和可用的重載函數選擇適當的函數。
為了說明這一點,printf在編譯時如下所示:
push value1
...
push valueN
push format_string
call _printf
printf的原型是這樣的:
int printf ( const char * format, ... );
因此,除了格式字符串中提供的內容之外,沒有任何類型信息會遺留。
通常對機制進行很好的解釋,然后再講技術性的語言。
使用字符串作為格式的一個很好的用途是,該格式實際上可以來自各種來源:其未在源代碼中進行硬編碼。您甚至可以要求用戶提供自己的格式字符串,或者將其與gettext一起使用以更改模式順序。
@ Max-P,是的,那絕對是優勢,但同時也是危險的填空游戲。 :)當然,無論如何,您都必須編寫一些包裝器,因為如果可以在命令行上提供格式字符串(可以這么說),則必須確保還提供了適當的參數。
push ... push call序列是實現printf調用的一種可能方式。實際的調用約定不是由C標準指定的(通常由ABI為平臺指定)。例如,某些參數可以在寄存器中傳遞,并且可以以任何順序傳遞。
@KeithThompson,我知道。這取決于實施和優化。但是,無論如何完成,都沒有關聯的類型信息,只有調用者指定的原始二進制值。
printf不是內部函數。它本身不是C語言的一部分。編譯器所做的全部工作就是生成代碼以調用printf,并傳遞任何參數。現在,由于C不提供反射作為在運行時找出類型信息的機制,因此程序員必須顯式提供所需的信息。
@Kevin Panko感謝您的出色編輯!就編輯而言,我仍然是菜鳥。
對兩位投票者:有幫助的解釋...
+1表示printf不是語言功能或任何特殊功能,因此編譯器沒有義務對其進行優化
@Tarik如果printf不是C語言的一部分,那么它實際上在哪里實現?通過操作系統?
編譯器可能很聰明,但是函數printf或scanf卻很愚蠢-它們不知道您為每次調用傳遞的參數的類型是什么。這就是為什么您需要每次通過%s或%d的原因。
您如何才能使這些功能更智能? C不支持反射。
在C++中,您可以:cin >> counter之類的東西;或cout << str;按預期工作。但是在C中您不能這樣做,這就是為什么發明了像%d這樣的字符串格式的原因。
在C ++中,cin / cout的示例不合適。之所以起作用,是因為重載,因此編譯器可以在編譯時決定它將為調用提供哪個函數。這是與varargs完全不同的機制。對于用戶來說,它看起來很相似,但從技術上講并不是這樣。
那是因為C ++支持運算符重載,這最終意味著您有一個用于<
我認為理論上編譯器可以插入正確的%x。
從理論上講,是的,但是字符串的格式是未知的。畢竟,您可以傳入任意字符串。從理論上講,在編譯時可以完成的工作基本上是像printf一樣解析格式字符串,并檢查參數是否正確。但是,那么您將必須為所有標準功能提供此功能。但是話又說回來,您可以打印一個字符串作為指針或字符串。
即使這樣也行不通:沒有什么可以阻止某人嘗試傳遞比int,double或char*更復雜的內容:例如,如果變量是某個怪異結構的指針。
@Timo從理論上講,可以做任何事情,但這將改變C的底層性質以及它所具有的靈活性。不再是C ...但是為什么要打擾呢? C ++已經存在。
第一個參數是格式字符串。如果您要打印一個十進制數字,它可能看起來像:
"%d"(十進制數)
"%5d"(十進制數字用空格填充到寬度5)
"%05d"(十進制數字用零填充到寬度5)
"%+d"(十進制數,始終帶有符號)
"Value: %d
"(數字前后的一些內容)
等等,請參閱例如Wikipedia上的Format占位符,以了解可以包含哪些格式字符串。
這里也可以有多個參數:
"%s - %d"(字符串,然后是一些內容,然后是數字)
Isn't the compiler matured enough to identify that when I declared my
variable?
沒有。
您使用的是數十年前指定的語言。不要指望C提供現代設計美學,因為它不是現代語言。現代語言將傾向于在編譯,解釋或執行中犧牲少量效率,以提高可用性或清晰度。從計算機處理時間昂貴且供應極為有限的時代開始,它的設計便體現了這一點。
這也是為什么當您真正在乎快速,高效或接近金屬時,C和C ++仍然是首選語言的原因。
那真是奇怪的評論。我從來沒有主張過。
是的,很抱歉,我將其寫到了錯誤的打開標簽中。抱歉給你帶來不便
GCC(可能還有其他C編譯器)至少在某些情況下會跟蹤參數類型。但是語言不是那樣設計的。
printf函數是一個接受變量參數的普通函數。可變參數需要某種運行時類型識別方案,但是在C語言中,值不攜帶任何運行時類型信息。 (當然,C程序員可以使用結構或位操作技巧創建運行時鍵入方案,但這些未集成到語言中。)
當我們開發這樣的函數時:
void foo(int a, int b, ...);
我們可以在第二個參數之后傳遞"任意"數量的附加參數,這取決于我們使用函數傳遞機制之外的某種協議來確定有多少個參數以及它們的類型。
例如,如果我們這樣調用此函數:
foo(1, 2, 3.0);
foo(1, 2,"abc");
被呼叫者無法區分案件。參數傳遞區域中只有一些位,我們不知道它們是表示字符數據的指針還是浮點數。
交流此類信息的可能性很多。例如,在POSIX中,exec系列函數使用的變量參數具有相同的類型char *,并且使用空指針來指示列表的結尾:
#include
void my_exec(char *progname, ...)
{
va_list variable_args;
va_start (variable_args, progname);
for (;;) {
char *arg = va_arg(variable_args, char *);
if (arg == 0)
break;
/* process arg */
}
va_end(variable_args);
/*...*/
}
如果調用者忘記傳遞空指針終止符,則該行為將是不確定的,因為該函數在使用完所有參數后將繼續調用va_arg。我們的my_exec函數必須這樣調用:
my_exec("foo","bar","xyzzy", (char *) 0);
需要對0進行強制類型轉換,因為沒有上下文可以將其解釋為空指針常量:編譯器不知道該參數的預期類型是指針類型。此外,(void *) 0是不正確的,因為它只是作為void *類型而不是char *類型傳遞,盡管幾乎可以肯定兩者在二進制級別兼容,所以它將在實踐中起作用。該類型的exec函數的常見錯誤是:
my_exec("foo","bar","xyzzy", NULL);
編譯器的NULL恰好被定義為0而沒有任何(void *)強制轉換。
另一種可能的方案是要求調用者傳遞一個數字,該數字指示有多少個參數。當然,該數字可能不正確。
在printf的情況下,格式字符串描述參數列表。該函數對其進行解析并相應地提取參數。
如開頭所述,某些編譯器,尤其是GNU C編譯器,可以在編譯時解析格式字符串,并根據參數的數量和類型執行靜態類型檢查。
但是,請注意,格式字符串可以不是文字字符串,并且可以在運行時計算
時間,這對于此類類型檢查方案是不可滲透的。虛構的例子:
char *fmt_string = message_lookup(current_language, message_code);
/* no type checking from gcc in this case: fmt_string could have
four conversion specifiers, or ones not matching the types of
arg1, arg2, arg3, without generating any diagnostic. */
snprintf(buffer, sizeof buffer, fmt_string, arg1, arg2, arg3);
scanf如原型int scanf ( const char * format, ... );所述,根據參數格式將給定數據存儲到附加參數所指向的位置。
它與編譯器無關,都與為scanf定義的語法有關。需要參數格式以使scanf知道要為輸入的數據保留的大小。
這是因為這是告訴函數(例如printf scanf)要傳遞哪種類型的值的唯一方法。例如-
int main()
{
int i=22;
printf("%c",i);
return 0;
}
此代碼將打印字符而不是整數22,因為您已告訴printf函數將變量視為char。
因為在printf中您沒有指定數據類型,所以您在指定數據格式。這在任何語言中都是重要的區別,在C語言中則是雙重重要的。
使用%s掃描字符串時,并不是說"為我的字符串變量解析字符串輸入"。您不能在C中這么說,因為C沒有字符串類型。 C與字符串變量最接近的是固定大小的字符數組,該數組恰巧包含表示字符串的字符,字符串的結尾由空字符表示。因此,您真正要說的是"這里有一個用于容納字符串的數組,我保證它足夠大,足以容納要解析的字符串輸入"。
原始?當然。 C是40多年前發明的,當時一臺典型的機器最多具有64K RAM。在這樣的環境中,保存RAM比復雜的字符串操作具有更高的優先級。
盡管如此,%s掃描程序仍然存在于更高級的編程環境中,在該環境中存在字符串數據類型。因為是關于掃描,而不是打字。
不,每個格式說明符都指定所需的參數類型。 %s需要類型為char*的參數(必須是指向字符串的指針)。 %d需要類型為int的參數。 %x和%o require和類型為unsigned int的參數。依此類推。 %s有點不同,它處理自變量指向的數據。哦,NULL不能指示字符串的結尾;多數民眾贊成在一個空的指針常量。它由空字符\0表示。
首先,您誤用了" requires"一詞。這意味著如果提供錯誤的數據類型,則會出現錯誤。您會收到警告,僅此而已。 %s期望char *,但是如果您提供其他內容,您仍然可以編譯。 //雖然您對NULL的看法是正確的,但是我會改變它。
我并不是說"需要"一詞暗示需要進行診斷。實際上,該行為是不確定的,并且提供錯誤的類型是錯誤的。 (它不需要診斷,因為通常這是不可能的;格式字符串不必是字符串文字。)通過提供"%s"或"%d"格式,程序員(您)將指定您提供或int分別作為對應的參數。
"這意味著如果提供錯誤的數據類型,您將得到一個錯誤" –不,不是。這是錯誤的,因此,在大多數情況下,這是您的答案。
@JimBalter告訴我林錯但不告訴我原因的評論不是很有用。
閱讀C標準以了解有關程序和實現的要求。僅需要針對約束錯誤提供診斷程序,但是還有許多其他要求,例如,正如Keith正確指出的那樣,%s需要類型為char*的參數。
當前的問題是"為什么C用這種方式做事?"這與標準無關-語言在存在標準之前就已經存在。如果您想了解一種語言的設計,則需要閱讀其基礎文檔。在這種情況下,Kernighan&Richie是一個不錯的起點。
printf和scanf是I / O函數,其設計和定義為接收控制字符串和參數列表的方式。
函數不知道傳遞給它的參數的類型,并且Compiler也無法將此信息傳遞給它。
-1據我所知,這并不能回答問題。
總結
以上是生活随笔為你收集整理的编译器构造c语言描述pdf,关于编译器构造:为什么每次都要在C中指定数据类型?...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 60分钟短线波段战术
- 下一篇: QT综合大作业—— 多媒体应用程序设计