簡體   English   中英

C89中的有效程序,但不是C99中的程序

[英]Valid programs in C89, but not in C99

是否在C99中引入或刪除了特征/語義,這些特性/語義也可以用C89編寫一個定義良好的程序

  • 無效(即根據C99標准不再編譯)
  • 編譯,但具有不同的語義。

到目前為止我的調查結果,涉及明顯無效的程序:

  • 隱式int(C89§3.5.2)
  • 隱函數聲明(C89§3.3.2.2)
  • 不從期望返回值的函數返回(C89§3.6.6.4)
  • 使用新關鍵字作為標識符(例如, restrictinline等)
  • 涉及//黑客,現在被視為評論。 但是,在生產代碼中幾乎從未遇到過。

微妙的變化,使相同的代碼具有不同的語義:

  • 已經很好地定義了整數除法,例如-3 / 2現在必須截斷為零(C99§6.5.5/ 6),而不是實現定義(C89§3.3.5/ 6)
  • strtod通過解析0x0X獲得了在C99中解析十六進制數的能力

我錯過了什么?

在C99出版之前,有許多程序在C89下被認為是有效的,有些人堅持認為這些程序永遠無效。 C89包含一條規則,該規則要求只能使用該類型的指針,相關類型或字符類型訪問任何類型的對象。 在C99發布之前,此規則通常被解釋為僅適用於“命名”對象(可通過名稱直接訪問的靜態或自動持續時間的變量),並且僅適用於有問題的對象沒有其地址的情況在它被用作不同的指針類型之前立即采取。 這種解釋受到許多因素的驅動:

  1. 該標准的既定目標之一是適應現有編譯器和程序正在做的事情,雖然現有程序很少使用不同類型的指針訪問離散命名變量,而不是在變量的地址被采用的情況下在這種使用之前,指針式打孔的許多其他用法都很常見。

  2. 標准的基本原理包括作為其唯一示例的函數,該函數接收一個基本類型的指針以寫入另一個基本類型的全局變量,使得編譯器沒有特別的理由期望別名。 能夠將全局變量保留在寄存器中顯然是一種有用的優化,並且規則的陳述目的是在編譯器沒有理由期望出現混疊的情況下允許這種優化。 禁止構造像like (int*)&foo=23; 沒有做任何事情來幫助這樣的優化,因為代碼占用foo的地址並解除引用它的事實應該使任何編譯器都非常清楚,這些編譯器並不是故意將代碼修改為foo

  3. 有許多種代碼需要在語義上將內存位用作各種類型的能力,標准中沒有任何內容表明規則旨在使程序員跳過箍(例如通過使用memcpy)來實現本來很容易的語義在沒有規則的情況下獲得,特別是考慮到使用memcpy會阻止編譯器在指針訪問中將全局變量保存在寄存器中(從而破壞了規則首先寫入的目的)

  4. 如果結構類型VW具有共同的初始序列,則U是包含兩者的任何聯合類型,並且p是標識U內的VV* ,則(W*)(U*)p可用於訪問那些普通成員,相當於(W*)p 除非編譯器能夠證明p不可能是指向包含W的某個聯合的成員的指針,否則將需要允許(W*)p訪問公共成員; 簡單地將這樣的普通成員訪問視為合法是更有幫助的,無論U是否存在或存在於何處,而不是尋找借口否認它。

  5. C89規則中沒有任何內容清楚地說明了如何定義分配存儲區域的“類型”,或者如何保存不再需要某種類型的東西的存儲可以重新用於保存另一個存儲的東西。

  6. 跟蹤分配給命名變量的寄存器比跟蹤分配給其他指針異常的寄存器更容易,並且有興趣通過指針最小化加載和存儲數量的代碼通常會將事物復制到命名變量並在那里處理它們。

C99添加了“有效類型”規則,這些規則明確適用於分配的存儲。 有些人堅持說這些僅僅是對C89中已經存在的規則的“澄清”,但由於上述原因,我發現這種觀點難以為繼。 現在聲稱編譯器沒有將別名規則應用於未命名對象的唯一原因是#5和#6,但反對#1-#4同樣重要(並且繼續適用於C99和C89一樣多)。 盡管如此,由於C99增加了有效的類型規則,因此很明顯禁止許多被C89規則的最常見解釋視為合法的結構。

作為對比和比較的一個要素, git / git代碼庫仍然嚴格符合C89,不使用C99初始化程序或新C標准的功能。
這在Git編碼指南的 Git 2.23(Q3 2019)中有詳細說明。

這個答案說明了可能與C89兼容的C89后功能。

Junio C gitstergitster 提交cc0c429 (2019年7月16日
(由Junio C gitster合並- gitster -提交fe9dc6b ,2017年7月25日)

CodingGuidelines:拼寫出C89之后的規則

雖然我們一直堅持使用C89,但是在我們的代碼庫中使用氣候氣球並且看到沒有人尖叫之后,我們從代碼庫中借用了更新的C語言。

把它們拼出來。

在它的同時,擴展現有的變量聲明規則,以便更好地使用for循環的新拼寫規則。

編碼指南現在包括

您不應該使用較新的C標准的功能,即使您的編譯器使用它們。

本指南有一些例外:

  • 從2012年初到e1327023ea (Git v1.7.9.2),我們一直在使用enum定義,其最后一個元素后面跟一個逗號
    這類似於以尾隨逗號結尾的數組初始值設定項,可用於在最后添加新標識符時減少補丁噪聲。

  • 自2017年中期開始使用cbc0f81d (Git v2.15.0-rc0),我們一直在使用struct的指定初始值設定項(例如“ struct tv = { .val = 'a' }; ”)
    某些C99功能在我們的代碼庫中可能會很好用,但為避免破壞與舊編譯器的兼容性,我們猶豫不決。
    但是我們實際上並不知道人們現在是否使用前C99編譯器。
    如果這個補丁可以在幾個版本中存活而沒有抱怨,那么我們可以更加自信,我們的用戶群廣泛支持指定的初始化程序。
    它還表明可能支持其他C99功能,但不是保證(例如, gcc在C99存在之前已指定初始化程序)。

  • 自2017年中期以來, 512f41cf (Git v2.15.0-rc0),我們一直在為數組使用指定的初始化器(例如“ int array[10] = { [5] = 2 } ”)。
    這是另一個測試氣球,看看我們是否收到了編譯器不支持數組指定初始化程序的人的投訴。
    這些曾經被禁止,但我們沒有聽到任何破損報告,並且它們被認為是安全的。

  • 變量必須在塊的開頭,在第一個語句之前聲明(即-Wdeclaration-after-statement )。

  • 在for code循環中聲明變量“ for (int i = 0; i < 10; i++) ”仍然不允許在此代碼庫中。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM