Khụng như Fortran, C là ngụn ngữ dạng tự do, cho phộp người lập trỡnh tựy tiện sử dụng kớ tự whitespaceđể xếp đặt (cỏch trỡnh bày) mó nguồn. Cỏc dũng chỳ giải hoặc cú thểđặt nằm giữa /*
và */ hoặc viết từng dũng bắt đầu bởi // theo sau đú là cỏc chỳ giải.
Mỗi tập tin chứa mó cú thể chứa cỏc khai bỏo và cỏc định nghĩa hàm. Cỏc định nghĩa hàm, chớnh chỳng, cũng chứa cỏc khai bỏo và cỏc mệnh đề. Cỏc khai bỏo thỡ hoặc là định nghĩa những kiểu mới với cỏc từ khoỏ nhưstruct, union, và enum, hoặc là gỏn cỏc kiểu và đăng kớ vựng chứa (trong bộ nhớ) cho cỏc biến mới (thớ dụ: *char myname = "ABC"). Cỏc từ khoỏ như là
char và int cũng như là kớ hiệu con trỏ chỉ tới * là cỏc kiểu sẵn cú. Cỏc khối mó thỡ được đúng ở
giữa cỏc dấu { và }để chỉ ra phần (mó) mà cỏc khai bỏo và cỏc cấu trỳc điều khiển (bờn trong dấu ngoặc) cú hiệu lực.
Như là một ngụn ngữ mệnh lệnh, C phụ thuộc vào cỏc mệnh đề (cõu lệnh) để làm hầu hết cỏc việc. Hầu hết cỏc mệnh đề lại là cỏc mệnh đề biểu thức mà một cỏch đơn giản chỳng tạo nờn việc đỏnh giỏ cỏc biểu thức đú -- và trong quỏ trỡnh này, cỏc biến nhận được cỏc giỏ trị mới hoặc cỏc giỏ trị này được trả ra. Cỏc mệnh đề dũng điều khiển cũng cú hiệu lực cho việc thực thi cú điều kiện hay cú lặp lại, mà chỳng được cấu tạo với cỏc từ khúa như là if, else, switch, do,
while và for. Cỏc nhảy dũng cũng cú thể thực hiện qua cõu lệnh goto. Nhiều phộp toỏn khỏc nhau
được cung cấp sẵn để thực thi trờn cỏc phộp tớnh cơ sản về số học, lụ gớc, so sỏnh, kiểu bit, chỉ
số của mảng, và phộp gỏn giỏ trị. Cỏc biểu thức cũng gọi cỏc hàm, bao gồm một số lượng lớn cỏc hàm thư viện, để tiến hành cỏc thao tỏc chung.
3. Cỏc vấn đề của C
Một cõu núi phổ biến được người ta lập lại nhiều lần của một nhà thiết kế trỡnh dịch Bjarne Stroustrup, người sỏng lập ra C++, là "C makes it easy to shoot yourself in the foot." (tạm dịch: "C làm cho việc bạn tự hại bạn trở nờn dễ dàng") [1]
Núi cỏch khỏc, C cho phộp nhiều phộp toỏn khụng mong muốn trong một cỏch tổng quỏt, và do đú, nhiều lỗi đơn giản đó được tạo ra bởi một người lập trỡnh mà chỳng lại khụng thể phỏt hiện qua trỡnh dịch hay ngay cả khụng phỏt hiện ra trong lỳc thi hành. Điều này là nguyờn nhõn của một số chương trỡnh cú cỏc ứng xử khụng lường trước được và cú cỏc lỗ hổng về an toàn. Một dị bản ngụn ngữ C là Cyclone điều chỉnh được một phần trong số cỏc vấn đề như vậy. Một lý do của cỏc vấn đề nờu trờn là để trỏnh cho cỏi giỏ quỏ cao phải trả cho việc kiểm soỏt (lỗi) ở thời gian dịch và thời gian thi hành. Một lý do khỏc là sự đũi hỏi để giữ C được càng hiệu quả và càng uyển chuyển càng tốt. Một ngụn ngữ càng mạnh thỡ càng khú khăn cho ngụn ngữđú để làm rừ ràng mọi thứ trong cỏc chương trỡnh (được viết trong ngụn ngữ này). Một số
việc kiểm tra đó được dựa trờn cỏc cụng cụ bờn ngoài, những cụng cụ như vậy được bàn đến trong phần Cỏc cụng cụ kiểm tra tĩnh bờn ngoài cho trỡnh dịch.
4. Sự cấp phỏt vựng nhớ
Một vấn đề với C (và đõy thường là vấn đề lớn cho những người mới làm quen với C) là việc cấp phỏt (vựng nhớ) một cỏch tự động hay một cỏch động cho cỏc đốỡ tượng mà khụng khởi
động chỳng. Cỏc đối tượng này, ban đầu, chứa cỏc giỏ trị bất kỡ trong khoảng nhớ mà chỳng
được làm sạch, chỳng hoàn toàn khụng dự đoỏn được. Nếu một chương trỡnh cú khai biến mà lại khụng gỏn giỏ trị ban đầu, thường là 0 (cho kiểu số) hay null (cho kiểu con trỏ) hay "" (cho kiểu dóy kớ tự, ...) thỡ cú thể gõy ra cỏc phản ứng khụng lường trước được của chương trỡnh đú. Hầu hết cỏc trỡnh dịch C hiện đại cú thể phỏt hiện và cảnh cỏo về việc "quờn gỏn giỏ trị khởi
động" trong nhiều trường hợp, nhưng cũng khụng hoàn toàn hiệu quả.
Một vấn đề thường thấy khỏc là bộ nhớheap khụng thểđược tỏi dụng cho tới khi nú được hoàn trả lại về cho bộ nhớ bởi người lập trỡnh bằng cõu lệnh free(). Hậu quả là nếu người lập trỡnh quờn hoàn trả cỏc vựng đó cấp phỏt về cho bộ nhớ và lại tiếp tục dựng cỏc lệnh cấp phỏt, thỡ càng lỳc càng nhiều cỏc phần của bộ nhớ bị chiếm chỗ. Lỗi này là một loại lỗi kiểu memory leak
tức là "rỉ bộ nhớ". Ngược lại, cũng cú trường hợp trả tự do phần đó cấp phỏt về cho bộ nhớ quỏ sớm và lại tiếp tục sử dụng vựng nhớđó trả về thỡ cũng cú thể dễ gõy ra việc nhận sai cỏc giỏ trị
hay tạo ra cỏc tỡnh huống khụng lường trước được. Lý do là vỡ mỏy tớnh khi nhận lại cỏc vựng
đó được trả sẽ cú thể dựng vựng nhớđú cho cỏc việc khỏc. Một số ngụn ngữ xử lý chuyện này với việc tựđộng dọn rỏc.
5. Cỏc con trỏ
Cỏc con trỏ là một nguồn gốc chớnh của nhiều nguy hiểm bởi vỡ chỳng khụng được kiểm tra, một con trỏ cú thểđược tạo ra để chỉ tới bất kỡ đối tượng nào bất kể kiểu nào, kể cả cỏc mó (nhị
phõn), và khi được dựng đến (hay được viết ra), cú thể gõy ra cỏc hiệu ứng khụng lường trước
được. Mặc dự hầu hết cỏc con trỏ thường chỉ tới cỏc chỗ an toàn, chỳng vẫn cú thể di chuyển tới những chỗ khụng an toàn như khi dựng cỏc phộp toỏn số học trờn cỏc con trỏ (thường là cộng trừ trờn cỏc địa chỉ mà chỳng chỉ tới), vựng nhớ chỗ chỳng chỉ tới cú thểđó được trả về và
đó được tỏi dụng (con trỏđu đưa), chỳng cú thểđó khụng được khởi động (con trỏ hoang), hay chỳng được trực tiếp gỏn một giỏ trị nào đú qua việc dựng toỏn tử đổi kiểu (cast) hoặc được gỏn qua một con trỏđó bị hủy hoại. Một vấn đề khỏc với cỏc con trỏ là việc C cho phộp tự do chuyển đổi giữa hai kiểu con trỏ bất kỡ. Cỏc ngụn ngữ khỏc điều chỉnh cỏc vấn đề này bằng cỏch dựng cỏc kiểu tham chiếu bị giới hạn hơn.
Mặc dự C cú hỗ trợ riờng cho cỏc mảng tĩnh, nhưng nú khụng kiểm tra xem cỏc chỉ số mảng cú hợp lệ hay khụng (kiểm tra biờn). Thớ dụ, người ta cú thể viết phần tử thứ 6 của một mảng được
định nghĩa với 5 phần tử, và điều này cú thể mang lại cỏc hậu quả khụng mong muốn. Lỗi này thuộc loại lỗi tràn bộ nhớđệm. Đõy là nguồn gốc của nhiều lỗ hổng an ninh trong cỏc chương trỡnh viết bằng C. Mặt khỏc, do sự giới hạn về kỹ thuật kiểm tra biờn ở thời điểm C ra đời (khi gần như chưa cú kỹ thuật kiểm tra biờn), nờn việc kiểm tra biờn trở nờn ảnh hưởng nặng đến tốc độ thực thi, đặc biệt là trong cỏc tớnh toỏn số.
Cỏc mảng đa chiều rất cần thiết khi cài đặt cỏc thuật toỏn số (chủ yếu ỏp dụng cho đại số tuyến tớnh) để chứa cỏc ma trận. Nhưng cấu trỳc mảng theo C khụng những khụng đỏp ứng mà cũn khụng tương hợp cho thao tỏc chuyờn biệt này. Vấn đề này đó được bàn thảo trong sỏch Numerical Recipes in C, chương 1.2, trang 20 ff (đọc trực tuyến). Người ta cú thể tỡm thấy ở đõy một giải phỏp tốt được dựng xuyờn suốt trong cả cuốn sỏch này.
6. Cỏc hàm tham lượng động
Một vấn đề thường thấy khỏc là về cỏc hàm tham lượng động (variadic function), tức là, cỏc hàm mà cú thể thay đổi được số lượng của cỏc tham số. Khụng giống như cỏc nguyờn mẫu khỏc của hàm trong C, kiểm tra số lượng tham số ở thời điểm dịch là khụng bắt buộc bởi tiờu chuẩn, và một cỏch tổng quỏt là khụng thể kiểm tra được nếu khụng cú thờm thụng tin. Nếu dữ
liệu cú kiểu khụng đỳng được chuyển vào, thỡ hậu quả sẻ khụng lường được, và thường tạo sự
hư hại hoàn toàn. Cỏc hàm tham lượng động cũng xử lý cỏc hằng số con trỏ rỗng trong một cỏch khụng biết trước được.
Thớ dụ: Họ cỏc hàm printf cung cấp bởi thư viện chuẩn, được dựng đểđịnh dạng cỏc dũng chữ
xuất ra, thỡ cú tiếng vỡ cỏc lỗi trong giao diện tham lượng động của nú; nú dựa trờn một sựđịnh dạng của dóy kớ tựđể biểu trưng số và kiểu của cỏc tham số theo sau.
Mặc dự kiểm tra kiểu của cỏc hàm tham lượng động từ thư viện chuẩn là một vấn đề về chất lượng của sự thiết lập, nhiều trỡnh dịch hiện đại đặc biệt tiến hành kiểm tra kiểu của việc gọi
printf, và sản sinh ra cỏc cảnh cỏo nếu danh mục tham số mà khụng tương ứng với dóy kớ tự định dạng. Dẫu sao thỡ khụng phải tất cả cỏc lần gọi printf đều cú thểđược kiểm tra một cỏch tĩnh bởi vỡ cú thể dóy kớ tựđịnh dạng chỉđược lập thành ở thời gian thực thi, khi mà cỏc hàm tham lượng động thường vẫn khụng kiểm tra được.
C cú nhiều điểm yếu trong cỳ phỏp. Đỏng chỳ ý là:
Nguyờn mẫu của hàm nào khụng chỉ ra tham số nào thỡ được hiểu ngầm là cho phộp một tập bất kỡ cỏc tham số. Một vấn đề về cỳ phỏp đó được đề ra cho khả năng tương thớch ngược của K&R C, về việc thiếu cỏc nguyờn mẫu.
Một số sự chọn lựa đỏng ngờ vực về thứ tựưu tiờn của cỏc toỏn tử, chẳng hạn như ==
"nối kết" một cỏch chặt chẽ hơn & và | trong cỏc biểu thức như là x & 1 == 0.
Việc dựng toỏn tử "=" một cỏch dễ nhầm lẫn. Nếu dựng trong đẳng thức toỏn học để chỉ
cỏc phộp gỏn, dẫn tới cỏc phộp gỏn khụng chủ định trong việc so sỏnh và dẫn tới một ấn tượng sai lầm rằng phộp gỏn cú tớnh bắc cầu. Thớ dụ: việc dựng cõu lệnh if (x=0) { ...} sẽ dễ gõy ra cỏc lỗi bất ngờ.
Thiếu cỏc toỏn tử infix cho cỏc đối tượng phức tạp, đặc biệt là cho cỏc phộp toỏn trờn dóy cỏc kớ tự làm cho chương trỡnh phụ thuộc nặng nề lờn cỏc phộp toỏn rất khú đọc.
Dựa vào quỏ nhiều trờn hệ thống kớ hiệu làm cơ sở cho cỳ phỏp ngay cảở nơi khụng tường minh như là "&&" và "||" thay vỡ dựng "and" và "or".
Cỳ phỏp khai bỏo khụng được dễ hiểu, đặc biệt cho hàm của cỏc con trỏ. Trong tỡnh huống hoàn toàn tương tự của C++, nhà nghiờn cứu Damian Conway núi về cỳ phỏp của khai bỏo như sau:
Khú để mà đặc tả một kiểu trong C++ bởi vỡ thực tế là một số phần tử của việc khai bỏo (như là con trỏ) thỡ là cỏc toỏn tử tiền tố trong khi một số khỏc (như là mảng) lại là toỏn tử hậu tố
(nghĩa là phải đặt *đứng trước tờn con trỏ và dặt [] sau tờn mảng -- người dịch). Nhừng toỏn tử
khai bỏo này lại cú cỏc thứ tự ưu tiờn khỏc nhau, cần phải được đặt trong cỏc dấu ngoặc cẩn thận đểđạt được sự khai bỏo mong muốn.
Ben Werther & Damian Conway.
A Modest Proposal: C++ Resyntaxed . Section 3.1.1. 1996. Cỏc vấn đề về bảo trỡ Cú một số vấn đề khỏc của C mà khụng trực tiếp do cỏc lỗi hay sai sút, nhưng lại làm ngăn trở khả năng của người lập trỡnh để xõy đựng một hệ thống cỡ lớn, cú thể bảo trỡ và ổn định. Cỏc điển hỡnh bao gồm: Hệ thống rời rạc vỡ cỏc cõu lệnh định nghĩa nhập (#include) dựa trờn cỏc dũng chữ nội tại rải rỏc khụng đồng nhất (trong cỏc tập tin) nhằm giữ cỏc nguyờn mẫu và cỏc định nghió được
đồng bộ. Điều này làm tăng đỏng kể số lần cho việc tạo dựng (phần mềm).
Mụ hỡnh chuyển dịch rối rắm. Nú buộc phải cú việc theo dừi cỏc sự phụ thuộc (về mó) bằng tay và nú ngăn cấm sự tối ưu húa trỡnh dịch giữa cỏc mụ dun (ngoại trừ sự tối ưu hoỏ thời gian liờn kết).
Hệ thống kiểu yếu dẫn tới việc cỏc chương trỡnh cú lỗi hiển nhiờn nhưng vẩn được dịch mà khụng bị bắt lỗi.
Nhiều cụng cụ đó được tạo ra để giỳp người lập trỡnh C trỏnh dược cỏc lỗi. Việc kiểm tra và kiểm toỏn mó nguồn tự động thỡ rất hiệu quả trong mọi ngụn ngữ. Chẳng hạn cho C là Lint. Một thực tế là sử dụng Lint để phỏt hiện cỏc mó cú nghi vấn khi một chương trỡnh được viết lần đầu. Một khi chương trỡnh đó qua được Lint, thỡ nú được chuyển dịch bởi một trỡnh dịch C. Cũng cú một thư viện cho việc tiến hành kiểm tra cỏc biờn của mảng và một dạng giới hạn của việc tựđộng dọn rỏc, nhưng đú khụng phải là một phần tiờu chuẩn của C.
Điều nờn nhận thức là cỏc cụng cụ này khụng phải là vạn năng. Bởi vỡ sự linh hoạt của C, nhiều kiểu lỗi như là việc dựng sai cỏc hàm tham lượng động, việc dựng chỉ số ngoài biờn của mảng và quản lý bộ nhớ khụng đỳng thỡ khụng thể phỏt hiện được. Mặc dự vậy, nhiều trường hợp (lỗi) thụng thường đều cú thểđược nhận ra.
7. Sự phỏt triển của ngụn ngữ C
Phỏt triển khởi đầu của C xảy ra ở AT&T Bell Labs giữa 1969 và 1973; theo Ritchie thỡ thời gian sỏng tạo nhất là vào năm 1972. Nú được đặt tờn là C vỡ nhiều đặc tớnh của nú rỳt ra từ một ngụn ngữ trước đú là B.
Thờm vào đú, cỏc điểm khỏc với ngụn ngữ nguyờn thủy "B": Ken Thompson kể tới ngụn ngữ
lập trỡnh BCPL, nhưng ụng ta cũng đó tạo ra ngụn ngữ là Bon để vinh danh vợ mỡnh.
Cú nhiều truyền thuyết về nguồn gốc của C và hệđiều hành liờn quan tới nú là Unix bao gồm: Sự phỏt triển của C là kết quả của cỏc lập trỡnh viờn đó muốn chơi Space Travel
. Họđó chơi nú trờn mainframe của hóng làm việc, nhưng bị thiếu khả năng (chạy) và phải hỗ
trợ khoảng 100 người dựng, Thompson và Ritchie tỡm thấy rằng họ đó khụng cú đủ sự kiểm soỏt tàu vũ trụ (của trũ chơi) để trỏnh được cỏc va chạm khỏi sự chuyển dịch của cỏc thiờn thạch. Do đú, họ quyết định để xuất trũ chơi này sang một mỏy PDP-7 để khụng trong văn phũng. Nhưng nú lại khụng cú hệ điều hành; do đú, họ viết một hệ điều hành. Tiếp tục, họ
quyết định để xuất hệđiều hành này sang PDP-11 của văn phũng nhưng việc này thật khú vỡ tất cả mó đều là ngụn ngữ Assembly. Họ quyết dịnh dựng một ngụn ngữ dễ xuất cấp cao để hệ điều hành cú thể xuất được dễ dàng từ mỏy tớnh này sang mỏy khỏc. Họ đó tỡm đến ngụn ngữ
B, nhưng nú lại thiếu cỏc chức năng để khai thỏc một số khả năng của PDP-11. Vậy nờn họđó sỏng tạo ra một ngụn ngữ mới là C.
Unix nguyờn đó được phỏt triển để tạo ra một hệ thống tựđộng lập hồ sơ cho cỏc bằng phỏt minh. Phiờn bản đầu tiờn của Unix đó phỏt triển từ ngụn ngữ Assembly. Sau đú, ngụn ngữ
C đó được phỏt triển để từđú thay thế hệđiều hành mới.
Cho đến 1973, C đó trở nờn đủ mạnh để dựng viết nhõn cho Unix, thay vỡ trước nú chỳng được viết bằng Assembly trong cỏc mỏy PDP-11/20. Đõy là lần đầu tiờn mà nhõn của một hệđiều hành được lắp thành bằng một ngụn ngữ khỏc hơn Assembly.
Năm 1978, Ritchie và Brian Kernighan xuất bản lần đầu cuốn The C Programming Language. Sỏch này được những người lập trỡnh biết tới như là "K&R", được dựng trong nhiều năm như
là một đặc tả khụng chớnh thức của C. Phiờn bản C mà cuốn sỏch đú đề cập thường được gọi là "K&R C". (Lần xuất bản thứ hai của cuốn này cũng bao gồm chuẩn ANSI C).
K&R giới thiệu cỏc chức năng sau đõy: Kiểu dữ liệu struct
Kiểu dữ liệu long int
Kiểu dữ liệu unsigned int
Toỏn tử =+ đó được đổi thành +=, và tương tự cho cỏc toỏn tử khỏc để trỏnh gõy hiểu nhầm cho bộ phõn tớch từ vựng của trỡnh dịch C. (Thớ dụ: sự giống nhau dể lầm lẫn của hai cõu lệnh i =+ 10 và i = +10).
K&R C thường được xem là phần cơ bản nhất của ngụn ngữ mà nú cần phải cú cho một trỡnh dịch C. Trong nhiều năm, ngay cả sau khi ANSI C được giới thiệu, nú đó được xem như là