รายละเอียดเล็กๆ น้อยๆ เกี่ยวกับฟังก์ชันหลัก อาร์กิวเมนต์ของฟังก์ชัน MAIN()

รูปแบบพื้นฐานของการเขียนฟังก์ชันมีดังนี้

return_type function_name(พารามิเตอร์_รายการ) { body_function) ประเภทของข้อมูลที่ส่งคืนโดยฟังก์ชันจะถูกระบุโดยใช้องค์ประกอบ return_type - ด้านล่างองค์ประกอบ พารามิเตอร์_รายการ หมายถึงรายการตัวแปรที่คั่นด้วยเครื่องหมายจุลภาคซึ่งสามารถรับอาร์กิวเมนต์ใดๆ ที่ส่งผ่านโดยฟังก์ชันได้

ใน C89 หากไม่ได้ระบุประเภทข้อมูลที่ส่งคืนโดยฟังก์ชันอย่างชัดเจน ระบบจะถือว่าประเภทข้อมูลที่ส่งกลับเป็น int ใน C++ และ C99 ไม่รองรับประเภท int เริ่มต้น แม้ว่าคอมไพเลอร์ C++ ส่วนใหญ่ยังคงใช้สมมติฐานนี้

ต้นแบบฟังก์ชัน

ในภาษา C++ ฟังก์ชันทั้งหมดต้องมีต้นแบบ และในภาษา C ต้นแบบเป็นทางเลือกอย่างเป็นทางการ แต่เป็นที่ต้องการอย่างมาก รูปแบบทั่วไปของคำจำกัดความต้นแบบมีดังนี้

return_type function_name(พารามิเตอร์_รายการ- ตัวอย่างเช่น. ลอย fn (ลอย x); //หรือ float fn(float);

ใน C คีย์เวิร์ด void จะถูกใช้แทนรายการพารามิเตอร์เพื่อระบุต้นแบบของฟังก์ชันที่ไม่มีพารามิเตอร์ ในภาษา C++ รายการพารามิเตอร์ว่างในฟังก์ชันต้นแบบหมายความว่าฟังก์ชันไม่มีพารามิเตอร์ คำว่าเป็นโมฆะเป็นทางเลือกในกรณีนี้

การส่งคืนค่า (คำสั่ง return)

การส่งคืนค่าในฟังก์ชันทำได้โดยใช้คำสั่ง return มันมีรูปแบบสัญกรณ์สองรูปแบบ

กลับ; กลับ ความหมาย;

ใน C99 และ C++ รูปแบบของคำสั่ง return ที่ไม่ได้ระบุค่าที่ส่งคืนควรใช้เฉพาะในฟังก์ชัน void เท่านั้น

ฟังก์ชั่นโอเวอร์โหลด

ใน C++ ฟังก์ชันสามารถทำได้ โอเวอร์โหลด- เมื่อฟังก์ชันถูกบอกว่าโอเวอร์โหลด หมายความว่าตั้งแต่สองฟังก์ชันขึ้นไปมีชื่อเดียวกัน แต่แต่ละเวอร์ชันของฟังก์ชันโอเวอร์โหลดจะมีจำนวนหรือประเภทของพารามิเตอร์ที่แตกต่างกัน ลองพิจารณาตัวอย่างฟังก์ชันโอเวอร์โหลดสามฟังก์ชันต่อไปนี้

ฟังก์ชั่นโมฆะ (int a) ( cout

ในแต่ละกรณี จะมีการวิเคราะห์ประเภทและจำนวนอาร์กิวเมนต์เพื่อกำหนดเวอร์ชันของ func() ที่จะเรียกใช้

ผ่านอาร์กิวเมนต์ของฟังก์ชันเริ่มต้น

ในภาษา C++ พารามิเตอร์ฟังก์ชันสามารถกำหนดค่าเริ่มต้นได้ ซึ่งจะถูกนำมาใช้โดยอัตโนมัติหากไม่มีการระบุอาร์กิวเมนต์ที่เกี่ยวข้องเมื่อเรียกใช้ฟังก์ชัน ตัวอย่างเช่น.

ฟังก์ชั่นโมฆะ (int a = 0, int b = 10)() //call func(); ฟังก์ชั่น(-1); ฟังก์ชั่น(-1, 99);

ขอบเขตและอายุการใช้งานของตัวแปร

ภาษา C และ C++ กำหนดกฎการมองเห็นที่สร้างแนวคิด เช่น ขอบเขตและอายุการใช้งานของออบเจ็กต์ มีขอบเขตระดับโลกและระดับท้องถิ่น

ทั่วโลกมีขอบเขตอยู่นอกขอบเขตอื่นๆ ทั้งหมด ชื่อที่ประกาศในขอบเขตส่วนกลางเป็นที่รู้จักของโปรแกรมทั้งหมด ตัวอย่างเช่น ตัวแปรร่วมสามารถใช้ได้กับทุกฟังก์ชันของโปรแกรม ตัวแปรโกลบอลมีอยู่ตลอดวงจรชีวิตของโปรแกรม

ท้องถิ่นขอบเขตจะถูกกำหนดโดยขอบเขตของบล็อก ชื่อที่ประกาศภายในขอบเขตท้องถิ่นจะทราบเฉพาะภายในขอบเขตนั้นเท่านั้น ตัวแปรท้องถิ่นจะถูกสร้างขึ้นเมื่อเข้าสู่บล็อกและถูกทำลายเมื่อออกจากบล็อก ซึ่งหมายความว่าตัวแปรท้องถิ่นจะไม่เก็บค่าไว้ระหว่างการเรียกใช้ฟังก์ชัน เพื่อรักษาค่าตัวแปรระหว่างการโทร คุณสามารถใช้ตัวแก้ไขแบบคงที่ได้

การเรียกซ้ำ

ใน C และ C++ ฟังก์ชันสามารถเรียกตัวเองได้ กระบวนการนี้เรียกว่า การเรียกซ้ำและฟังก์ชันที่เรียกตัวเองว่าเป็นฟังก์ชันแบบเรียกซ้ำ ตามตัวอย่าง ลองใช้ฟังก์ชัน fact() ซึ่งคำนวณแฟกทอเรียลของจำนวนเต็ม

Int fact (int n) ( int ans; if (n == 1) ส่งคืน 1; ans = fact (n-1) * n; return ans; )

ฟังก์ชั่นหลัก

การดำเนินการของโปรแกรม C/C++ เริ่มต้นด้วยการดำเนินการของฟังก์ชัน main() (โปรแกรม Windows เรียกใช้ฟังก์ชัน WinMain());

ฟังก์ชัน main() ไม่มีต้นแบบ ดังนั้นจึงสามารถใช้ฟังก์ชัน main() ได้หลายรูปแบบ ฟังก์ชัน main() รูปแบบต่างๆ ต่อไปนี้ใช้ได้กับทั้ง C และ C++

Int หลัก(); int หลัก (int argc, ถ่าน * argv)

ดังที่เห็นได้จากรูปแบบที่สองของการบันทึก f main() ยอมรับพารามิเตอร์อย่างน้อยสองตัว พวกเขาถูกเรียกว่า argc และ argv อาร์กิวเมนต์เหล่านี้จะจัดเก็บจำนวนอาร์กิวเมนต์บรรทัดคำสั่งและตัวชี้ตามลำดับ argc เป็นประเภทจำนวนเต็มและค่าของมันจะเป็นอย่างน้อย 1 เสมอ เนื่องจากเช่นเดียวกับใน C และ C++ อาร์กิวเมนต์แรกจะเป็นชื่อของโปรแกรมเสมอ พารามิเตอร์ argv จะต้องประกาศเป็นอาร์เรย์ของตัวชี้อักขระ โดยแต่ละองค์ประกอบจะชี้ไปที่อาร์กิวเมนต์บรรทัดคำสั่ง ด้านล่างนี้เป็นตัวอย่างโปรแกรมที่สาธิตการใช้อาร์กิวเมนต์เหล่านี้

#รวม ใช้เนมสเปซมาตรฐาน; int main (int argc, char *argv) ( ถ้า (argc

การผ่านพอยน์เตอร์

แม้ว่าภาษา C และ C++ จะใช้พารามิเตอร์ที่ส่งผ่านค่าตามค่าเริ่มต้น แต่คุณสามารถสร้างการเรียกไปยัง f ได้ด้วยตนเอง ด้วยพารามิเตอร์ที่ส่งผ่านโดยการอ้างอิง ในการดำเนินการนี้ คุณจะต้องส่งพอยน์เตอร์ไปยังอาร์กิวเมนต์ เนื่องจากในกรณีนี้ฟังก์ชันจะถูกส่งผ่านที่อยู่ของอาร์กิวเมนต์ ปรากฎว่ามีความเป็นไปได้ที่จะเปลี่ยนค่าของอาร์กิวเมนต์ภายนอกฟังก์ชัน- ตัวอย่างเช่น.

สลับโมฆะ (int *x, int *y) ( int temp; temp = *x; ​​​​*x = *y; *y = temp; ) // สลับการโทร (&a, &b);

ในภาษา C++ ที่อยู่ของตัวแปรสามารถส่งผ่านไปยังฟังก์ชันได้โดยอัตโนมัติ สิ่งนี้ถูกนำไปใช้งานโดยใช้ พารามิเตอร์อ้างอิง- เมื่อใช้พารามิเตอร์อ้างอิง ที่อยู่ของอาร์กิวเมนต์จะถูกส่งผ่านไปยังฟังก์ชัน และฟังก์ชันจะทำงานกับอาร์กิวเมนต์แทนที่จะเป็นการคัดลอก หากต้องการสร้างพารามิเตอร์ลิงก์ คุณต้องนำหน้าชื่อด้วยเครื่องหมายแอมเปอร์แซนด์ (&) ภายในฉ. พารามิเตอร์นี้สามารถใช้ได้ตามปกติ โดยไม่ต้องใช้ตัวดำเนินการเครื่องหมายดอกจัน (*) เป็นต้น

สลับโมฆะ (int &x, int &y)( int temp; temp = x; x = y; y = temp; ) // สลับการโทร (a, b);

ตัวระบุฟังก์ชัน

C++ กำหนดตัวระบุฟังก์ชันสามตัว:

  • แบบอินไลน์
  • เสมือน
  • ชัดเจน

ตัวระบุแบบอินไลน์เป็นการร้องขอไปยังคอมไพเลอร์: แทนที่จะสร้างการเรียกใช้ฟังก์ชัน ให้ขยายโค้ดโดยตรงบนบรรทัด หากคอมไพลเลอร์ไม่สามารถแทรกฟังก์ชันลงในสตริงได้ ก็มีสิทธิ์ที่จะเพิกเฉยต่อข้อกำหนดนี้ ตัวระบุแบบอินไลน์สามารถกำหนดทั้งฟังก์ชันสมาชิกและฟังก์ชันที่ไม่ใช่สมาชิก

เป็นฟังก์ชันเสมือน (โดยใช้ตัวระบุเสมือน) ​​f. ถูกกำหนดไว้ในคลาสพื้นฐานและแทนที่ในคลาสที่ได้รับ เมื่อใช้ฟังก์ชันเสมือนเป็นตัวอย่าง คุณจะเข้าใจได้ว่าภาษา C++ สนับสนุนความหลากหลายได้อย่างไร

ตัวระบุที่ชัดเจนจะใช้กับตัวสร้างเท่านั้น ตัวสร้างที่กำหนดว่าชัดเจนจะถูกใช้ก็ต่อเมื่อการกำหนดค่าเริ่มต้นตรงกับสิ่งที่ตัวสร้างระบุไว้ทุกประการ จะไม่มีการดำเนินการแปลงใดๆ (เช่น ตัวระบุที่ชัดเจนจะสร้าง "ตัวสร้างที่ไม่แปลง")

เทมเพลตฟังก์ชัน

รูปแบบทั่วไปของการกำหนดฟังก์ชันเทมเพลตมีดังนี้

ประเภทเทมเพลต> return_type function_name (พารามิเตอร์_รายการ) ( //function body ) ที่นี่ พิมพ์หมายถึงป้ายกำกับตัวยึดสำหรับประเภทข้อมูลที่ฟังก์ชันนี้จะจัดการจริง ในคำสั่งเทมเพลต คุณสามารถกำหนดพารามิเตอร์ประเภทข้อมูลได้หลายประเภทโดยใช้รูปแบบของรายการองค์ประกอบที่คั่นด้วยเครื่องหมายจุลภาค

ลองดูตัวอย่าง

แม่แบบ การแลกเปลี่ยนเป็นโมฆะ (X &a, X &b) ( X temp; temp = a; a = b; b = temp; ) // โทร int a, b; ลอย x, y; สลับ(ก, ข); สลับ(x, y);

พอยน์เตอร์ฟังก์ชัน

คุณสามารถสร้างตัวชี้ไปยังฟังก์ชันได้ เช่นเดียวกับวัตถุภาษา C อื่นๆ ไวยากรณ์มีดังนี้

return_type (*index_name)(ตัวแปร_คำอธิบาย- การประกาศนี้จะสร้างตัวชี้ไปยังฟังก์ชันที่เรียกว่า index_name ซึ่งมีตัวแปรอยู่ ตัวแปร_คำอธิบาย และส่งคืนค่าประเภทใด return_type .

สามารถเรียกใช้ฟังก์ชันได้โดยใช้พอยน์เตอร์ดังนี้

index_name = function_name; ตัวแปร = index_name (ตัวแปร- ที่นี่บรรทัดแรกสร้างการอ้างอิงฟังก์ชัน function_name- บรรทัดที่สองเรียกใช้ฟังก์ชันผ่านตัวชี้จริงๆ index_name ซึ่งตัวแปรต่างๆ จะถูกส่งผ่านไปยัง ตัวแปรค่าจะถูกส่งกลับไปยังตัวแปร ตัวแปร .

ตัวอย่างต่อไปนี้สาธิตการสร้างพอยน์เตอร์และสองวิธีในการเรียกใช้ฟังก์ชันผ่านพอยน์เตอร์ ตลอดจนการส่งฟังก์ชันเป็นพารามิเตอร์ไปยังฟังก์ชันอื่นๆ

สองเท่า y; สองเท่า (*p)(สองเท่า x); พี=บาป; //สร้างตัวชี้ไปยังฟังก์ชัน sin() y = (*p)(2.5); //เรียก y = p(2.5); //call //ส่งฟังก์ชันเป็นพารามิเตอร์ double y; double f(double (*c)(double x), double y)( return c(y); // เรียกใช้ฟังก์ชันที่ส่งผ่านไปยังตัวชี้ c แล้วส่งคืนค่า ) y = f(sin, 2.5); //ส่งฟังก์ชัน sin ไปยังฟังก์ชัน f รวมถึงพารามิเตอร์ที่จะถูกประมวลผล

คุณยังสามารถสร้างอาร์เรย์ของตัวชี้ฟังก์ชันได้

Int f1(เป็นโมฆะ); int f2(เป็นโมฆะ); int f3(เป็นโมฆะ); int (*p)(เป็นโมฆะ) = (f1, f2, f3) y = (*p)(); //การเรียกใช้ฟังก์ชัน f2 y = p(); //เรียกใช้ฟังก์ชัน f3

ความเป็นไปได้ของภาษาตระกูล C นั้นไร้ขีด จำกัด อย่างแท้จริงอย่างไรก็ตามในเสรีภาพนี้ก็มีข้อเสียเช่นกัน: โปรแกรมเมอร์จำเป็นต้องจับตาดูและควบคุม "บัฟเฟอร์ล้น" อยู่เสมอ เพื่อที่ว่าในภายหลังโปรแกรมจะไม่ชนกับ " หน้าจอสีน้ำเงิน” บน Windows และฮาร์ดแวร์ของผู้ใช้หลายเวอร์ชัน แครกเกอร์และรีเวิร์สเดียวกันนี้มองหาช่องโหว่ในโค้ดของโปรแกรม C ซึ่งสามารถฝังโค้ดไวรัสได้ ผู้เขียนได้พูดคุยเกี่ยวกับเรื่องนี้โดยละเอียดในหลักสูตรวิดีโอของเขา ฉันได้เรียนรู้มากมายจากที่นั่น และตอนนี้โค้ดของฉันก็ปลอดภัยมากขึ้น

ฟังก์ชั่นหลัก

โปรแกรม C และ C++ ทุกโปรแกรมต้องมีฟังก์ชันหลัก และมันก็ขึ้นอยู่กับคุณว่าจะวางมันไว้ที่ไหน โปรแกรมเมอร์บางคนวางไว้ที่จุดเริ่มต้นของไฟล์ บางส่วนอยู่ท้ายไฟล์ อย่างไรก็ตาม ไม่ว่าตำแหน่งนั้นจะอยู่ที่ใด คุณต้องจำสิ่งต่อไปนี้: อาร์กิวเมนต์ของฟังก์ชัน "main" ขั้นตอนการเริ่มต้น Borland C++ จะส่งพารามิเตอร์ (อาร์กิวเมนต์) สามตัวไปยังฟังก์ชันหลัก: argc, argv และ env - argc ซึ่งเป็นจำนวนเต็มคือจำนวนอาร์กิวเมนต์บรรทัดคำสั่งที่ส่งไปยังฟังก์ชันหลัก - argv คืออาร์เรย์ของพอยน์เตอร์ไปยังสตริง (อักขระ *) ภายใต้ DOS 3.x และใหม่กว่า argv ถูกกำหนดให้เป็นเส้นทางแบบเต็มของโปรแกรมที่จะเปิดตัว เมื่อทำงานภายใต้ DOS เวอร์ชันก่อนหน้า argv จะชี้ไปที่สตริงว่าง ("") argv ชี้ไปที่บรรทัดคำสั่งแรกหลังชื่อโปรแกรม argv ชี้ไปที่บรรทัดคำสั่งที่สองหลังชื่อโปรแกรม argv ชี้ไปที่อาร์กิวเมนต์สุดท้ายที่ส่งไปที่ main argv มีค่า NULL - env ​​​​ยังเป็นอาร์เรย์ของพอยน์เตอร์ไปยังสตริง แต่ละองค์ประกอบ env มีสตริงในรูปแบบ ENVVAR=value ENVVAR คือชื่อของตัวแปรสภาพแวดล้อม เช่น PATH หรือ 87<значение>นี่คือค่าของตัวแปรสภาพแวดล้อมที่กำหนด เช่น C:\DOS;C:\TOOLS (สำหรับ PATH) หรือ YES (สำหรับ 87) อย่างไรก็ตาม โปรดทราบว่าหากคุณระบุอาร์กิวเมนต์เหล่านี้บางส่วน คุณต้องระบุอาร์กิวเมนต์ตามลำดับนี้: argc, argv, env ตัวอย่างเช่น การประกาศอาร์กิวเมนต์ต่อไปนี้ถูกต้อง: main() main(int argc) /* valid but not very good */ main(int argc, char *argv) main(int argc, char *argv, char *env) Main (int) การประกาศ argc) ไม่สะดวกนักเนื่องจากการทราบจำนวนพารามิเตอร์ คุณไม่สามารถเข้าถึงพารามิเตอร์เหล่านั้นได้ด้วยตัวเอง อาร์กิวเมนต์ env สามารถเข้าถึงได้ตลอดเวลาผ่านตัวแปรส่วนกลางของสภาพแวดล้อม ดูตัวแปรสภาพแวดล้อม (ในบทที่ 3) และฟังก์ชัน putenv และ getenv (ในบทที่ 2) พารามิเตอร์ argc และ argv ยังพร้อมใช้งานผ่านตัวแปร _argc และ _argv ตัวอย่างโปรแกรมที่ใช้ argc, argv และ env นี่คือโปรแกรมตัวอย่าง ARGS.EXE ซึ่งสาธิตวิธีที่ง่ายที่สุดในการใช้อาร์กิวเมนต์ที่ส่งไปยังฟังก์ชันหลัก /* โปรแกรม ARGS.C */ #include #รวม เป็นโมฆะ main(int argc, char *argv, char *env) ( int i; printf("ค่าของ argc คือ %d \n\n",argc); printf("บรรทัดคำสั่งประกอบด้วยพารามิเตอร์ %d \n\ n" ,argc);<=argc; i++) printf(" argv[%d]: %s\n",i,argv[i]); printf("Среда содержит следующие строки:\n"); for (i=0; env[i] != NULL; i++) printf(" env[%d]: %s\n",i,env[i]); return 0; } Предположим, что вы запускаете программу ARGS.EXE со следующей командной строкой: C:> args first_arg "arg มีช่องว่าง" 3 4 "สุดท้ายแต่มีอันเดียว" หยุด! โปรดทราบว่าคุณสามารถส่งอาร์กิวเมนต์ที่มีช่องว่างได้โดยใส่เครื่องหมายคำพูดคู่ ดังที่แสดงในตัวอย่าง "อาร์กิวเมนต์ที่มีช่องว่าง" และ "สุดท้ายแต่มีเพียงหนึ่งเดียว" ในการเรียกโปรแกรม จากการรันโปรแกรมคุณจะได้สิ่งนี้: ค่าของ argc คือ 7 บรรทัดคำสั่งประกอบด้วยพารามิเตอร์ 7 ตัว argv: c:\turboc\testargs.exe argv: first_arg argv: arg พร้อม argv ว่างเปล่า: 3 argv: 4 argv: สุดท้าย แต่หนึ่ง argv: หยุด! สภาพแวดล้อมประกอบด้วยบรรทัดต่อไปนี้: env: COMSPEC=C:\COMMAND.COM env: PROMPT=$p $g env: PATH=C:\SPRINT;C:\DOS;C:\BC ความยาวรวมสูงสุดของคำสั่ง บรรทัดที่ส่งไปยังฟังก์ชันหลัก (รวมถึงช่องว่างและชื่อของโปรแกรมเอง) ต้องมีความยาวไม่เกิน 128 อักขระ สิ่งเหล่านี้เป็นข้อจำกัดของ DOS อักขระหลีกบรรทัดคำสั่ง อาร์กิวเมนต์บรรทัดคำสั่งสามารถมีอักขระหลีกได้ อย่างไรก็ตาม พวกเขาสามารถขยายชื่อไฟล์ทั้งหมดที่ตรงกับอาร์กิวเมนต์ได้ในลักษณะเดียวกับที่ทำเสร็จแล้ว เช่น ด้วยคำสั่งคัดลอก DOS หากต้องการใช้สัญลักษณ์หลีก เมื่อเชื่อมโยงโปรแกรมของคุณกับตัวเชื่อมโยง คุณต้องรวมไฟล์อ็อบเจ็กต์ WILDARGS.OBJ ที่มาพร้อมกับ Borland C++ หากแนบไฟล์ WILDARGS.OBJ เข้ากับโปรแกรมของคุณ คุณสามารถใช้อาร์กิวเมนต์ เช่น "*.*" บนบรรทัดคำสั่งได้ ในกรณีนี้ ชื่อของไฟล์ทั้งหมดที่ตรงกับมาสก์นี้จะถูกป้อนลงในอาร์เรย์ argv ขนาดสูงสุดของอาร์เรย์ argv ขึ้นอยู่กับขนาดของพื้นที่หน่วยความจำแบบไดนามิกเท่านั้น หากไม่พบไฟล์ที่เหมาะสมสำหรับมาสก์ที่กำหนด อาร์กิวเมนต์จะถูกส่งผ่านในรูปแบบที่พิมพ์บนบรรทัดคำสั่ง (นั่นคือ ฟังก์ชันหลักจะถูกส่งผ่านสตริงที่มีอักขระหลีก) อาร์กิวเมนต์ที่อยู่ในเครื่องหมายคำพูดคู่ ("...") จะไม่ถูกขยาย ตัวอย่าง. คำสั่งต่อไปนี้คอมไพล์ไฟล์ ARGS.C และลิงก์ไปยังโมดูล WILDARGS.OBJ จากนั้นรันโปรแกรม ARGS.EXE ที่เป็นผลลัพธ์: bcc args wildarg.obj args C:\BORLANDC\INCLUDE\*.H "*.C" เมื่อรัน ARGS.EXE อาร์กิวเมนต์แรกจะขยายเป็นชื่อของไฟล์ทั้งหมดที่มีนามสกุล H ในไดเร็กทอรี Borland C++ INCLUDE โปรดทราบว่าทุกบรรทัดรวมเส้นทางแบบเต็ม (เช่น C:\TC\INCLUDE\ALLOC.H) อาร์กิวเมนต์ *.C ไม่ได้ถูกขยายเนื่องจาก มันอยู่ในเครื่องหมายคำพูด หากคุณกำลังทำงานใน Integrated Environment (BC.EXE) คุณเพียงแค่ต้องระบุชื่อไฟล์โปรเจ็กต์ในเมนูโปรเจ็กต์ ซึ่งควรมีบรรทัดต่อไปนี้: ARGS WILDARGS.OBJ จากนั้นใช้คำสั่ง "Run/Arguments คำสั่ง " คุณควรตั้งค่าพารามิเตอร์บรรทัดคำสั่ง ความคิดเห็น หากคุณต้องการให้การประมวลผลอักขระหลีกเกิดขึ้นเสมอ เช่น เพื่อให้ WILDARGS.OBJ เชื่อมโยงโดยอัตโนมัติโดยตัวแก้ไขลิงก์ คุณต้องแก้ไขไลบรารี C?.LIB มาตรฐานของคุณเพื่อรวมไฟล์ WILDARGS.OBJ เมื่อต้องการทำเช่นนี้ ให้ลบ SETARGV ออกจากไลบรารี และเพิ่ม WILDARGS ซึ่งสามารถทำได้ด้วยคำสั่งต่อไปนี้ (เราถือว่าไลบรารีมาตรฐานและ WILDARGS.OBJ มีอยู่ในไดเร็กทอรีปัจจุบัน): TLIB มีอธิบายไว้ในบทที่ 7 ยูทิลิตี้ ของคู่มือผู้ใช้ tlib cs -setargv +wildargs tlib cc - setargv +wildargs tlib cm -setargv +wildargs tlib cl -setargv +wildargs tlib ch -setargv +wildargs การคอมไพล์โดยใช้สวิตช์ -p (แบบแผนการเรียก Pascal) หากคุณคอมไพล์โปรแกรมของคุณโดยใช้แบบแผนการเรียก Pascal (อธิบายรายละเอียดไว้ในแบบแผนการเรียก Pascal) ) บทที่ 9 "การเชื่อมต่อกับภาษาแอสเซมบลี", "คู่มือโปรแกรมเมอร์") คุณต้องจำไว้ว่าฟังก์ชันหลักจะต้องได้รับการประกาศอย่างชัดเจนว่าเป็นฟังก์ชัน C ซึ่งสามารถทำได้โดยใช้คีย์เวิร์ด cdecl ดังนี้: cdecl main(int argc , char *argv, char *env) ค่าที่ส่งคืนโดยฟังก์ชันหลัก ฟังก์ชั่นหลักส่งคืนค่าที่เป็นโค้ดออกของโปรแกรม: นี่คือจำนวนเต็ม อย่างไรก็ตาม หากโปรแกรมของคุณใช้ฟังก์ชัน exit (หรือ _exit) เพื่อออก ค่าที่ส่งคืนจะเป็นอาร์กิวเมนต์ของฟังก์ชันนั้น ตัวอย่างเช่น หากโปรแกรมของคุณมีการเรียก: exit(1) รหัสทางออกจะเป็น 1 หากคุณใช้ Borland C++ integrated Environment (BC.EXE) เพื่อรันโปรแกรม คุณจะสามารถดูค่าที่ส่งคืนของโปรแกรมหลักได้ ฟังก์ชั่นโดยเลือก "ไฟล์ | รับข้อมูล"

โปรแกรม C++ ขั้นต่ำคือ

Int main() ( ) // โปรแกรม C++ ขั้นต่ำ

โปรแกรมนี้ประกอบด้วยการประกาศฟังก์ชัน หลักซึ่งไม่ยอมรับข้อโต้แย้งใดๆ เครื่องหมายปีกกาแสดงถึงการจัดกลุ่มในภาษา C++ และในกรณีนี้จะแสดงเนื้อความของฟังก์ชัน หลัก.นั่นคือจุดเริ่มต้นของฟังก์ชันหลักคือวงเล็บเปิด และจุดสิ้นสุดของฟังก์ชันหลักคือวงเล็บปิด เครื่องหมายทับคู่บ่งบอกถึงจุดเริ่มต้นของความคิดเห็น ความคิดเห็นจะถูกละเว้นโดยคอมไพเลอร์และทำหน้าที่ชี้แจงข้อมูลในโค้ด

ทุกโปรแกรมที่เขียนด้วยภาษา C++ มีฟังก์ชัน หลัก()ซึ่งโปรแกรมจะเริ่มทำงาน การทำงาน หลัก(),ตามกฎแล้วจะส่งคืนผลลัพธ์ของการดำเนินการซึ่งส่งสัญญาณโดย ภายใน(จำนวนเต็ม - จำนวนเต็ม) ซึ่งเขียนไว้หน้าฟังก์ชัน หลัก()- เมื่อถูกต้องและสำเร็จแล้วฟังก์ชัน หลัก()กลับเป็นผล 0 - ค่าผลลัพธ์อื่นที่ไม่ใช่ศูนย์จะส่งสัญญาณถึงการยกเลิกโปรแกรมอย่างผิดปกติ

ค่าที่ส่งคืนโดยโปรแกรมเมื่อเสร็จสิ้นสามารถนำมาใช้ในระบบปฏิบัติการเพื่อการบริการได้

ตัวอย่างทั่วไปของโปรแกรมแรกในภาษาการเขียนโปรแกรมใดๆ ก็คือเอาต์พุตข้อความ "สวัสดีชาวโลก!":

#รวม int main() ( std::cout<< "Hello, World!\n"; }

แต่ทุกอย่างเรียบง่ายในโปรแกรมนี้เหรอ? โดยทั่วไป โปรแกรมขนาดเล็กนี้เพียงอย่างเดียวประกอบด้วยชั้นข้อมูลขนาดใหญ่มากที่ต้องเข้าใจในการพัฒนาด้วย C++

  1. คำสั่ง #รวม
    #รวม
    บอกคอมไพเลอร์ว่าจำเป็นต้องรวมไฟล์ส่วนหัวบางไฟล์ซึ่งมีการวางแผนส่วนประกอบที่จะใช้ในไฟล์ที่มีการประกาศฟังก์ชัน หลัก() . ไอโอสตรีมเป็นไลบรารี I/O มาตรฐานจาก STL นั่นคือฟังก์ชันการทำงานของไลบรารีถูกใช้แล้วที่นี่ แม้ว่าจะเป็นมาตรฐานสำหรับภาษาก็ตาม และจุดสุดท้ายคือวงเล็บเหลี่ยมซึ่งมีชื่อของไลบรารีซึ่งระบุว่านี่คือการรวมไฟล์ภายนอกไว้ในโปรเจ็กต์ ไม่ใช่ไฟล์ที่เป็นส่วนหนึ่งของโปรเจ็กต์ ไฟล์เดียวกันที่เป็นส่วนหนึ่งของโครงการจะถูกรวมไว้ในเครื่องหมายคำพูดปกติ เป็นต้น #รวม "myclass.h"การเชื่อมต่อของไลบรารีนี้เป็นมาตรฐาน ตัวอย่างเช่นใน วิชวลสตูดิโอการไม่ปฏิบัติตามมาตรฐานนี้จะส่งผลให้เกิดข้อผิดพลาด
  2. มาตรฐานคือการใช้เนมสเปซซึ่งมีคำสั่งเอาต์พุตอยู่ ศาลเนมสเปซถูกนำมาใช้ใน C ++ เพื่อลบความขัดแย้งของชื่อระหว่างไลบรารีและโปรเจ็กต์ของผู้พัฒนา หากมีฟังก์ชันหรือชื่อคลาสที่ซ้ำกันอยู่ที่ไหนสักแห่ง Java ใช้ระบบแพ็กเกจเพื่อแก้ไขข้อขัดแย้งของชื่อ

    ศาลเป็นตัวดำเนินการเอาต์พุตที่มีตัวดำเนินการโอเวอร์โหลด << เพื่อหลีกเลี่ยงการใช้ฟังก์ชันแยกต่างหากในการส่งออกข้อความไปยังคอนโซล

นอกเหนือจากการเขียนฟังก์ชันแล้ว หลักอาจมีรูปแบบที่แตกต่างกัน แม้ว่าสองรายการจะเป็นมาตรฐาน:

  1. int หลัก()
  2. int หลัก (int argc, ถ่าน * argv)

คุณยังสามารถค้นหาบันทึกเช่น เป็นโมฆะหลัก ()ฯลฯ แต่รายการเหล่านี้เป็นรายการที่ผิดพลาด แม้ว่าคอมไพเลอร์บางตัวจะคอมไพล์ แม้ว่าจะไม่มีข้อผิดพลาดและคำเตือนก็ตาม

ในการบันทึก int หลัก (int argc, ถ่าน * argv)อาร์กิวเมนต์ถูกส่งผ่าน:

  1. อาร์กิวเมนต์- ระบุจำนวนข้อโต้แย้งที่ส่งผ่าน อย่างน้อย 1 เสมอ เนื่องจากชื่อโปรแกรมจะถูกส่งผ่านเสมอ
  2. หาเรื่อง- อาร์เรย์ของพอยน์เตอร์ไปยังอาร์กิวเมนต์ที่ถูกส่งผ่านเป็นตัวแปรสตริง

ถ้า อาร์กิวเมนต์มากกว่า 1 หมายความว่ามีการส่งผ่านอาร์กิวเมนต์เพิ่มเติมเมื่อเปิดตัวโปรแกรม

เช็คอาจมีลักษณะดังนี้:

#รวม int main(int argc, char* argv) ( // หากอาร์กิวเมนต์เพิ่มเติมถูกส่งผ่าน ถ้า (argc > 1) ( // จากนั้นเราจะพยายามพิมพ์อาร์กิวเมนต์ที่ได้รับ std::cout<< argv<

โดยทั่วไปแล้ว มีหลายประเด็นที่ต้องเข้าใจในภาษา C++ แม้ว่าจะเป็นโปรแกรมขนาดเล็กก็ตาม แต่นี่กลับทำให้น่าสนใจยิ่งขึ้นเท่านั้น ;-)

วันหนึ่งฉันเริ่มสนใจเนื้อหาของสแต็กของฟังก์ชันหลักของกระบวนการใน Linux ฉันทำการวิจัยและตอนนี้ฉันนำเสนอผลลัพธ์ให้คุณทราบ

ตัวเลือกสำหรับการอธิบายฟังก์ชันหลัก:
1. int หลัก ()
2. int main(int argc, char **argv)
3. int main(int argc, char **argv, char **env)
4. int main(int argc, char **argv, char **env, ElfW(auxv_t) auxv)
5. int main(int argc, ถ่าน **argv, ถ่าน **env, ถ่าน **apple)

Argc - จำนวนพารามิเตอร์
argv - อาร์เรย์เทอร์มินัล null ของพอยน์เตอร์ไปยังสตริงพารามิเตอร์บรรทัดคำสั่ง
env คืออาร์เรย์เทอร์มินัล null ของพอยน์เตอร์ไปยังสตริงของตัวแปรสภาพแวดล้อม แต่ละบรรทัดในรูปแบบ NAME=VALUE
auxv - อาร์เรย์ของค่าเสริม (ใช้ได้กับ PowerPC เท่านั้น)
apple - พาธไปยังไฟล์ปฏิบัติการ (บน MacOS และ Darwin)
เวกเตอร์เสริมคืออาร์เรย์ที่มีข้อมูลเพิ่มเติมต่างๆ เช่น ตัวระบุผู้ใช้ที่มีประสิทธิภาพ คุณลักษณะบิต setuid ขนาดหน้าหน่วยความจำ เป็นต้น

ขนาดของส่วนสแต็กสามารถดูได้ในไฟล์แผนที่:
cat /proc/10918/maps.cat

7ffffffa3000-7ffffffff000 rw-p 00000000 00:00 0

ก่อนที่ตัวโหลดจะถ่ายโอนการควบคุมไปยังตัวโหลดหลัก ตัวโหลดจะเริ่มต้นเนื้อหาของอาร์เรย์ของพารามิเตอร์บรรทัดคำสั่ง ตัวแปรสภาพแวดล้อม และเวกเตอร์เสริม
หลังจากการกำหนดค่าเริ่มต้น ด้านบนของสแต็กจะมีลักษณะเช่นนี้สำหรับเวอร์ชัน 64 บิต
ที่อยู่อาวุโสด้านบน

1. 0x7ffffffff000 จุดสูงสุดของส่วนสแต็ก การโทรทำให้เกิด segfault
0x7ffffffff0f8 โมฆะ เป็นโมฆะ* 8 0x00"
2. ชื่อไฟล์ ถ่าน 1+ "/tmp/a.out"
ถ่าน 1 0x00
...
สิ่งแวดล้อม ถ่าน 1 0x00
...
ถ่าน 1 0x00
3. 0x7fffffffe5e0 สิ่งแวดล้อม ถ่าน 1 ..
ถ่าน 1 0x00
...
หาเรื่อง ถ่าน 1 0x00
...
ถ่าน 1 0x00
4. 0x7fffffffe5be หาเรื่อง ถ่าน 1+ "/tmp/a.out"
5. อาร์เรย์ที่มีความยาวสุ่ม
6. ข้อมูลสำหรับ auxv เป็นโมฆะ* 48"
AT_NULL เอลฟ์64_auxv_t 16 {0,0}
...
auxv เอลฟ์64_auxv_t 16
7. auxv เอลฟ์64_auxv_t 16 เช่น: (0x0e,0x3e8)
โมฆะ เป็นโมฆะ* 8 0x00
...
สิ่งแวดล้อม ถ่าน* 8
8. 0x7fffffffe308 สิ่งแวดล้อม ถ่าน* 8 0x7fffffffe5e0
โมฆะ เป็นโมฆะ* 8 0x00
...
หาเรื่อง ถ่าน* 8
9. 0x7fffffffe2f8 หาเรื่อง ถ่าน* 8 0x7fffffffe5be
10. 0x7fffffffe2f0 อาร์กิวเมนต์ ยาว 8" จำนวนอาร์กิวเมนต์ + 1
11. ตัวแปรท้องถิ่นและอาร์กิวเมนต์ของฟังก์ชันที่ถูกเรียกก่อน main
12. ตัวแปรโลคัลหลัก
13. 0x7fffffffe1fc อาร์กิวเมนต์ ภายใน 4 จำนวนอาร์กิวเมนต์ + 1
0x7fffffffe1f0 หาเรื่อง ถ่าน** 8 0x7fffffffe2f8
0x7fffffffe1e8 สิ่งแวดล้อม ถ่าน** 8 0x7fffffffe308
14. ตัวแปรฟังก์ชันท้องถิ่น

" - ฉันไม่พบคำอธิบายของฟิลด์ในเอกสาร แต่มองเห็นได้ชัดเจนในดัมพ์

ฉันไม่ได้ตรวจสอบ 32 บิต แต่ส่วนใหญ่ก็เพียงพอที่จะแบ่งขนาดเป็นสอง

1. การเข้าถึงที่อยู่ที่อยู่เหนือจุดสูงสุดทำให้เกิด Segfault
2. สตริงที่มีเส้นทางไปยังไฟล์ปฏิบัติการ
3. อาร์เรย์ของสตริงที่มีตัวแปรสภาพแวดล้อม
4. อาร์เรย์ของสตริงพร้อมพารามิเตอร์บรรทัดคำสั่ง
5. อาร์เรย์ที่มีความยาวสุ่ม การเลือกสามารถปิดใช้งานได้ด้วยคำสั่ง
sysctl -w kernel.randomize_va_space=0
เสียงสะท้อน 0 > /proc/sys/kernel/randomize_va_space
6. ข้อมูลสำหรับเวกเตอร์เสริม (เช่น สตริง “x86_64”)
7. เวกเตอร์เสริม รายละเอียดเพิ่มเติมด้านล่าง
8. อาร์เรย์ Null-terminal ของพอยน์เตอร์ไปยังสตริงของตัวแปรสภาพแวดล้อม
9. อาร์เรย์ Null-terminal ของพอยน์เตอร์ไปยังสตริงพารามิเตอร์บรรทัดคำสั่ง
10. คำเครื่องที่มีจำนวนพารามิเตอร์บรรทัดคำสั่ง (หนึ่งในอาร์กิวเมนต์ของฟังก์ชัน "หลัก" ดูย่อหน้าที่ 11)
11. ตัวแปรท้องถิ่นและอาร์กิวเมนต์ของฟังก์ชันที่ถูกเรียกก่อน main(_start,__libc_start_main..)
12. ตัวแปรที่ประกาศใน main
13.ข้อโต้แย้งของฟังก์ชันหลัก
14. ตัวแปรและอาร์กิวเมนต์ของฟังก์ชันท้องถิ่น

เวกเตอร์เสริม
สำหรับ i386 และ x86_64 ไม่สามารถรับที่อยู่ขององค์ประกอบแรกของเวกเตอร์เสริมได้ แต่สามารถรับเนื้อหาของเวกเตอร์นี้ได้ด้วยวิธีอื่น หนึ่งในนั้นคือการเข้าถึงพื้นที่หน่วยความจำที่อยู่ด้านหลังอาร์เรย์ของพอยน์เตอร์ไปยังสตริงของตัวแปรสภาพแวดล้อม
มันควรมีลักษณะดังนี้:
#รวม #รวม int main(int argc, char** argv, char** env)( Elf64_auxv_t *auxv; //x86_64 // Elf32_auxv_t *auxv; //i386 while(*env++ != NULL); // มองหาจุดเริ่มต้นของ เวกเตอร์เสริมสำหรับ ( auxv = (Elf64_auxv_t *)env; auxv->a_type != AT_NULL; auxv++)( printf("addr: %p type: %lx is: 0x%lx\n", auxv, auxv->a_type, auxv->a_un .a_val); printf("\n (เป็นโมฆะ*)(*argv) - (เป็นโมฆะ*)auxv= %p - %p = %ld\n (เป็นโมฆะ*)(argv)-(เป็นโมฆะ* )(&auxv) =%p-%p = %ld\n ", (เป็นโมฆะ*)(*argv), (เป็นโมฆะ*)auxv, (เป็นโมฆะ*)(*argv) - (เป็นโมฆะ*)auxv, (เป็นโมฆะ* )(argv) , (เป็นโมฆะ*)(&auxv), (เป็นโมฆะ*)(argv) - (เป็นโมฆะ*)(&auxv)); printf("\n สำเนา argc: %d\n",*((int *) (argv - 1 ))); กลับ 0;
โครงสร้าง Elf(32,64)_auxv_t มีอธิบายไว้ใน /usr/include/elf.h ฟังก์ชั่นสำหรับการเติมโครงสร้างใน linux-kernel/fs/binfmt_elf.c

วิธีที่สองในการรับเนื้อหาของเวกเตอร์:
hexdump /proc/self/auxv

การแสดงที่อ่านได้ง่ายที่สุดได้มาโดยการตั้งค่าตัวแปรสภาพแวดล้อม LD_SHOW_AUXV

LD_SHOW_AUXV=1 ลิตร
AT_HWCAP: bfebfbff // ความสามารถของโปรเซสเซอร์
AT_PAGESZ: 4096 // ขนาดหน้าหน่วยความจำ
AT_CLKTCK: 100 // อัปเดตความถี่ครั้ง ()
AT_PHDR: 0x400040 //ข้อมูลส่วนหัว
AT_PHENT: 56
AT_PHNUM: 9
AT_BASE: 0x7fd00b5bc000 //ที่อยู่ล่าม นั่นคือ ld.so
AT_FLAGS: 0x0
AT_ENTRY: 0x402490 // จุดเริ่มต้นของโปรแกรม
AT_UID: 1,000 // ตัวระบุผู้ใช้และกลุ่ม
AT_EUID: 1,000 // ระบุและมีประสิทธิภาพ
AT_GID: 1,000
AT_EGID: 1,000
AT_SECURE: 0 // ไม่ว่าจะยกธง setuid หรือไม่
AT_RANDOM: 0x7fff30bdc809 // ที่อยู่ของ 16 ไบต์แบบสุ่ม
สร้างขึ้นเมื่อเริ่มต้น
AT_SYSINFO_EHDR: 0x7fff30bff000 //ตัวชี้ไปยังหน้าที่ใช้สำหรับ
//การเรียกของระบบ
AT_EXECFN: /bin/ls
AT_PLATFORM: x86_64
ด้านซ้ายเป็นชื่อของตัวแปร ด้านขวาเป็นค่า ชื่อตัวแปรที่เป็นไปได้ทั้งหมดและคำอธิบายสามารถพบได้ในไฟล์ elf.h (ค่าคงที่พร้อมคำนำหน้า AT_)

กลับจาก main()
หลังจากเตรียมใช้งานบริบทของกระบวนการแล้ว การควบคุมจะไม่ถูกถ่ายโอนไปยัง main() แต่ไปยังฟังก์ชัน _start()
main() ถูกเรียกจาก __libc_start_main แล้ว ฟังก์ชันสุดท้ายนี้มีคุณสมบัติที่น่าสนใจ โดยจะส่งผ่านตัวชี้ไปยังฟังก์ชันที่ควรดำเนินการหลังจาก main() และตัวชี้นี้จะถูกส่งผ่านสแต็กตามธรรมชาติ
โดยทั่วไป อาร์กิวเมนต์ของ __libc_start_main จะมีลักษณะเช่นนี้ ตามไฟล์ glibc-2.11/sysdeps/ia64/elf/start.S
/*
* อาร์กิวเมนต์สำหรับ __libc_start_main:
* out0: หลัก
* out1: argc
* out2: argv
* out3: เริ่มต้น
* out4: fini //ฟังก์ชันถูกเรียกตาม main
* out5: rtld_fini
* out6: stack_end
*/
เหล่านั้น. ในการรับที่อยู่ของตัวชี้ฟินิ คุณจะต้องเลื่อนคำของเครื่องสองคำจากตัวแปรหลักภายในตัวสุดท้าย
นี่คือสิ่งที่เกิดขึ้น (ความสามารถในการใช้งานได้ขึ้นอยู่กับเวอร์ชันของคอมไพเลอร์):
#รวม เป็นโมฆะ ** เกษียณ; เป็นโมฆะ * ออก; ถือเป็นโมฆะ foo())( void (*boo)(void); //function pointer printf("Stack rewrite!\n"); boo = (void (*)(void))leave; boo(); // fini () ) int main(int argc, char *argv, char *envp) ( int mark แบบยาวที่ไม่ได้ลงชื่อ = 0xbfbfbfbfbfbfbfbf; // ทำเครื่องหมายที่เราจะใช้งาน ret = (void**)(&mark+2); // แยกไฟล์ ที่อยู่ ฟังก์ชันที่ถูกเรียกหลังจากเสร็จสิ้น (fini) leave = *ret; //จำ *ret = (void*)foo; // return 0; // function call foo() )

ฉันหวังว่ามันน่าสนใจ
ขอให้โชคดี.

ขอบคุณผู้ใช้ Xeor สำหรับเคล็ดลับที่เป็นประโยชน์

คุณสามารถส่งผ่านข้อโต้แย้งไปยังโปรแกรม C ได้ เมื่อเรียก main() เมื่อเริ่มต้นการคำนวณ จะมีการส่งผ่านพารามิเตอร์สามตัว อันแรกกำหนดจำนวนอาร์กิวเมนต์คำสั่งเมื่อเข้าถึงโปรแกรม ประการที่สองคืออาร์เรย์ของพอยน์เตอร์ไปยังสตริงอักขระที่มีอาร์กิวเมนต์เหล่านี้ (หนึ่งอาร์กิวเมนต์ต่อบรรทัด) ตัวที่สามยังเป็นอาร์เรย์ของพอยน์เตอร์ไปยังสตริงอักขระ มันถูกใช้เพื่อเข้าถึงพารามิเตอร์ระบบปฏิบัติการ (ตัวแปรสภาพแวดล้อม)

บรรทัดใด ๆ ดังกล่าวจะแสดงเป็น:

ตัวแปร = ค่า\0

บรรทัดสุดท้ายสามารถพบได้โดยศูนย์สองตัวต่อท้าย

ลองตั้งชื่ออาร์กิวเมนต์ของฟังก์ชัน main() ตามนั้น: argc, argv และ env (ชื่ออื่นก็ได้) จากนั้นจึงยอมรับคำอธิบายต่อไปนี้:

หลัก (int argc, ถ่าน * argv)

หลัก (int argc, ถ่าน *argv, ถ่าน *env)

สมมติว่าบนไดรฟ์ A: มีโปรแกรม prog.exe บางตัว มาพูดถึงเรื่องนี้กัน:

A:\>ไฟล์ prog.exe1 ไฟล์2 ไฟล์3

จากนั้น argv เป็นตัวชี้ไปยังบรรทัด A:\prog.exe argv เป็นตัวชี้ไปยังบรรทัด file1 เป็นต้น อาร์กิวเมนต์จริงตัวแรกชี้ไปที่ argv และอาร์กิวเมนต์สุดท้ายชี้ไปที่ argv หาก argc=1 แสดงว่าไม่มีพารามิเตอร์หลังชื่อโปรแกรมบนบรรทัดคำสั่ง ในตัวอย่างของเรา argc=4

การเรียกซ้ำ

การเรียกซ้ำเป็นวิธีการเรียกที่ฟังก์ชันอ้างถึงตัวมันเอง

จุดสำคัญในการเขียนโปรแกรมแบบเรียกซ้ำคือการจัดระเบียบเอาต์พุต ข้อผิดพลาดง่ายๆ ที่ทำที่นี่คือฟังก์ชันจะเรียกตัวเองตามลำดับโดยไม่มีกำหนด ดังนั้น กระบวนการแบบเรียกซ้ำต้องค่อยๆ ลดความซับซ้อนของปัญหา เพื่อให้วิธีแก้ปัญหาแบบไม่เรียกซ้ำปรากฏขึ้นในที่สุด การใช้การเรียกซ้ำนั้นไม่เป็นที่ต้องการเสมอไป เนื่องจากอาจทำให้เกิดการล้นของสแต็กได้

ฟังก์ชั่นห้องสมุด

ในระบบการเขียนโปรแกรม รูทีนสำหรับการแก้ปัญหาที่พบบ่อยจะถูกรวมเข้าไว้ในไลบรารี งานดังกล่าวประกอบด้วย: การคำนวณฟังก์ชันทางคณิตศาสตร์ ข้อมูลอินพุต/เอาท์พุต การประมวลผลสตริง การโต้ตอบกับเครื่องมือระบบปฏิบัติการ ฯลฯ การใช้รูทีนของไลบรารีช่วยลดความจำเป็นในการพัฒนาเครื่องมือที่เหมาะสมแก่ผู้ใช้ และให้บริการเพิ่มเติมแก่ผู้ใช้ ฟังก์ชั่นที่รวมอยู่ในไลบรารีนั้นมาพร้อมกับระบบการเขียนโปรแกรม การประกาศจะระบุไว้ในไฟล์ *.h (ซึ่งเรียกว่าไฟล์รวมหรือไฟล์ส่วนหัว) ดังนั้น ตามที่กล่าวไว้ข้างต้น เมื่อเริ่มต้นโปรแกรมที่มีฟังก์ชันไลบรารีควรมีบรรทัดดังนี้:

#รวม<включаемый_файл_типа_h>

ตัวอย่างเช่น:

#รวม

นอกจากนี้ยังมีสิ่งอำนวยความสะดวกในการขยายและสร้างไลบรารีใหม่ด้วยโปรแกรมผู้ใช้

ตัวแปรโกลบอลได้รับการจัดสรรตำแหน่งคงที่ในหน่วยความจำตลอดระยะเวลาของโปรแกรม ตัวแปรท้องถิ่นจะถูกเก็บไว้ในสแต็ก ระหว่างนั้นมีพื้นที่หน่วยความจำสำหรับการจัดสรรแบบไดนามิก

ฟังก์ชัน malloc() และ free() ใช้เพื่อจัดสรรหน่วยความจำว่างแบบไดนามิก ฟังก์ชัน malloc() จัดสรรหน่วยความจำ ส่วนฟังก์ชัน free() จะทำให้หน่วยความจำว่าง ต้นแบบของฟังก์ชันเหล่านี้ถูกจัดเก็บไว้ในไฟล์ส่วนหัว stdlib.h และมีลักษณะดังนี้:

เป็นโมฆะ * malloc (ขนาด size_t);

เป็นโมฆะ * ฟรี (เป็นโมฆะ * p);

ฟังก์ชัน malloc() ส่งคืนตัวชี้โมฆะ เพื่อการใช้งานที่เหมาะสม ค่าฟังก์ชันจะต้องถูกแปลงเป็นตัวชี้ให้เป็นประเภทที่เหมาะสม หากสำเร็จ ฟังก์ชันจะส่งคืนตัวชี้ไปยังไบต์แรกของหน่วยความจำว่างที่มีขนาด หากมีหน่วยความจำไม่เพียงพอ ค่าจะถูกส่งกลับ 0 เพื่อกำหนดจำนวนไบต์ที่จำเป็นสำหรับตัวแปร ให้ใช้การดำเนินการ sizeof()

ตัวอย่างการใช้ฟังก์ชันเหล่านี้:

#รวม

#รวม

p = (int *) malloc(100 * ขนาดของ (int)); /* จัดสรรหน่วยความจำเป็น 100

จำนวนเต็ม */

printf("หน่วยความจำไม่เพียงพอ\n");

สำหรับ (i = 0; i< 100; ++i) *(p+i) = i; /* Использование памяти */

สำหรับ (i = 0; i< 100; ++i) printf("%d", *(p++));

ฟรี(พี); /* หน่วยความจำว่าง */

ก่อนที่จะใช้ตัวชี้ที่ส่งคืนโดย malloc() คุณต้องแน่ใจว่ามีหน่วยความจำเพียงพอ (ตัวชี้ไม่เป็นค่าว่าง)

พรีโปรเซสเซอร์

ตัวประมวลผลล่วงหน้า AC เป็นโปรแกรมที่ประมวลผลอินพุตไปยังคอมไพเลอร์ โปรเซสเซอร์ล่วงหน้าจะดูที่โปรแกรมต้นทางและดำเนินการต่อไปนี้: เชื่อมต่อไฟล์ที่ระบุเข้ากับโปรแกรม ดำเนินการทดแทน และยังควบคุมเงื่อนไขการคอมไพล์ด้วย บรรทัดโปรแกรมที่ขึ้นต้นด้วยสัญลักษณ์ # มีไว้สำหรับพรีโปรเซสเซอร์ อนุญาตให้เขียนคำสั่งเดียวเท่านั้น (คำสั่งตัวประมวลผลล่วงหน้า) ในหนึ่งบรรทัด

คำสั่ง

#define การทดแทนตัวระบุ

ทำให้ตัวระบุที่มีชื่อถูกแทนที่ในข้อความโปรแกรมต่อมาด้วยข้อความแทนที่ (โปรดสังเกตว่าไม่มีเครื่องหมายอัฒภาคที่ส่วนท้ายของคำสั่งนี้) โดยพื้นฐานแล้ว คำสั่งนี้จะแนะนำคำจำกัดความของแมโคร โดยที่ "identifier" คือชื่อของคำจำกัดความของแมโคร และ "การทดแทน" คือลำดับของอักขระที่ตัวประมวลผลล่วงหน้าแทนที่ชื่อที่ระบุเมื่อพบในข้อความของโปรแกรม เป็นเรื่องปกติที่จะพิมพ์ชื่อของคำนิยามแมโครด้วยตัวพิมพ์ใหญ่

ลองดูตัวอย่าง:

บรรทัดแรกทำให้โปรแกรมแทนที่ตัวระบุ MAX ด้วยค่าคงที่ 25 บรรทัดที่สองให้คุณใช้คำว่า BEGIN ในข้อความแทนการใช้เครื่องหมายปีกกาเปิด (()

โปรดทราบว่าเนื่องจากตัวประมวลผลล่วงหน้าไม่ได้ตรวจสอบความเข้ากันได้ระหว่างชื่อเชิงสัญลักษณ์ของคำจำกัดความของแมโครและบริบทที่ใช้ ขอแนะนำให้กำหนดตัวระบุดังกล่าวไม่ใช่ด้วยคำสั่ง #define แต่ใช้คีย์เวิร์ด const ที่มีการบ่งชี้อย่างชัดเจนของ ประเภท (สิ่งนี้ใช้กับ C+ + ในระดับที่มากขึ้น):

ค่าคงที่ int MAX = 25;

(ประเภท int สามารถละเว้นได้ เนื่องจากเป็นค่าเริ่มต้น)

หากคำสั่ง #define มีลักษณะดังนี้:

#define identifier (ตัวระบุ, ..., ตัวระบุ) ​​การทดแทน

และไม่มีช่องว่างระหว่างตัวระบุแรกและวงเล็บเปิด นี่คือคำจำกัดความของการแทนที่แมโครด้วยอาร์กิวเมนต์ ตัวอย่างเช่น หลังจากบรรทัดเช่น:

#define READ(val) scanf("%d", &val)

คำสั่ง READ(y); ปฏิบัติเช่นเดียวกับ scanf("%d",&y);. ที่นี่ val คืออาร์กิวเมนต์และดำเนินการทดแทนแมโครด้วยอาร์กิวเมนต์

หากมีคำจำกัดความที่ยาวในการทดแทนที่ดำเนินต่อไปในบรรทัดถัดไป อักขระ \ จะถูกวางไว้ที่ท้ายบรรทัดถัดไป

คุณสามารถใส่วัตถุที่คั่นด้วย ## ลงในคำจำกัดความของแมโครได้ เช่น:

#กำหนด PR(x, y) x##y

หลังจากนี้ PR(a, 3) จะเรียกการทดแทน a3 หรือตัวอย่างเช่น คำจำกัดความของแมโคร

#define z(a, b, c, d) a(b##c##d)

จะส่งผลให้แทนที่ z(sin, x, +, y) ด้วย sin(x+y)

สัญลักษณ์ # วางไว้หน้าอาร์กิวเมนต์แมโครบ่งชี้ว่าถูกแปลงเป็นสตริง เช่น หลังจากคำสั่ง

#define PRIM(var) printf(#var"= %d", var)

ส่วนของข้อความโปรแกรมต่อไปนี้

ถูกแปลงดังนี้:

printf("ปี""= %d", ปี);

มาอธิบายคำสั่งตัวประมวลผลล่วงหน้าอื่น ๆ กัน เคยเห็นคำสั่ง #include มาก่อน สามารถใช้ได้ในสองรูปแบบ:

#รวม "ชื่อไฟล์"

#รวม<имя файла>

การกระทำของทั้งสองคำสั่งคือการรวมไฟล์ที่มีชื่อที่ระบุไว้ในโปรแกรม ไฟล์แรกจะโหลดไฟล์จากไดเร็กทอรีปัจจุบันหรือไดเร็กทอรีที่ระบุเป็นคำนำหน้า คำสั่งที่สองค้นหาไฟล์ในตำแหน่งมาตรฐานที่กำหนดไว้ในระบบการเขียนโปรแกรม หากไม่พบไฟล์ที่ชื่อเขียนด้วยเครื่องหมายคำพูดคู่ในไดเร็กทอรีที่ระบุ การค้นหาจะดำเนินต่อไปในไดเร็กทอรีย่อยที่ระบุสำหรับคำสั่ง #include<...>- #include คำสั่งสามารถซ้อนกันภายในกันและกันได้

คำสั่งกลุ่มถัดไปช่วยให้คุณสามารถรวบรวมบางส่วนของโปรแกรมได้ กระบวนการนี้เรียกว่าการรวบรวมแบบมีเงื่อนไข กลุ่มนี้ประกอบด้วยคำสั่ง #if, #else, #elif, #endif, #ifdef, #ifndef รูปแบบพื้นฐานของการเขียนคำสั่ง #if มีลักษณะดังนี้:

#if ลำดับการแสดงออกคงที่ของคำสั่ง

ที่นี่จะมีการตรวจสอบค่าของนิพจน์คงที่ หากเป็นจริง ลำดับของคำสั่งที่ระบุจะถูกดำเนินการ และหากเป็นเท็จ ลำดับของคำสั่งนี้จะถูกข้ามไป

การทำงานของคำสั่ง #else จะคล้ายกับการทำงานของคำสั่ง else ในภาษา C ตัวอย่างเช่น:

#ถ้าคงที่_การแสดงออก

คำสั่ง_ลำดับ_2

ในที่นี้ หากนิพจน์คงที่เป็นจริง ก็จะถูกดำเนินการoperator_sequence_1 และหากเป็นเท็จ จะดำเนินการoperator_sequence_2

คำสั่ง #elif หมายถึงการดำเนินการ "else if" รูปแบบการใช้งานพื้นฐานคือ:

#ถ้าคงที่_การแสดงออก

คำสั่ง_ลำดับ

#เอลิฟ คงที่_expression_1

คำสั่ง_ลำดับ_1

#เอลิฟ คงที่_expression_n

ลำดับ_ของ_คำสั่ง_n

แบบฟอร์มนี้คล้ายกับโครงสร้างภาษา C: if...else if...else if...

คำสั่ง

#ตัวระบุ ifdef

กำหนดว่าตัวระบุที่ระบุถูกกำหนดไว้ในปัจจุบันหรือไม่ เช่น ไม่ว่าจะรวมอยู่ในคำสั่งเช่น #define หรือไม่ สตริงของแบบฟอร์ม

#ตัวระบุ ifndef

ตรวจสอบว่าตัวระบุที่ระบุไม่ได้ถูกกำหนดไว้ในปัจจุบันหรือไม่ คำสั่งใดๆ เหล่านี้สามารถตามด้วยข้อความจำนวนบรรทัดใดก็ได้ โดยอาจมีคำสั่ง #else (ไม่สามารถใช้ #elif ได้) และลงท้ายด้วยบรรทัด #endif หากเงื่อนไขที่กำลังตรวจสอบเป็นจริง เส้นทั้งหมดระหว่าง #else และ #endif จะถูกละเว้น และหากเป็นเท็จ เส้นระหว่างการตรวจสอบและ #else (หากไม่มีคำว่า #else จะไม่มีคำว่า #endif) คำสั่ง #if และ #ifndef สามารถซ้อนกันภายในกันและกันได้

ดูคำสั่ง

#undef ตัวระบุ

ทำให้ตัวระบุที่ระบุถูกพิจารณาว่าไม่ได้กำหนดไว้ เช่น ไม่สามารถทดแทนได้

ลองดูตัวอย่าง คำสั่งสามประการต่อไปนี้:

ตรวจสอบว่ามีการกำหนดตัวระบุ WRITE หรือไม่ (เช่น มีคำสั่งเช่น #define WRITE...) หรือไม่ และหากเป็นเช่นนั้น ชื่อ WRITE จะเริ่มถือว่าไม่ได้กำหนด เช่น ไม่สามารถทดแทนได้

คำสั่ง

#define เขียน fprintf

ตรวจสอบว่าตัวระบุ WRITE ไม่ได้ถูกกำหนดไว้หรือไม่ และหากเป็นเช่นนั้น ตัวระบุ WRITE จะถูกกำหนดแทนชื่อ fprintf

คำสั่ง #error ถูกเขียนในรูปแบบต่อไปนี้:

#ข้อผิดพลาด ข้อผิดพลาด_ข้อความ

หากเกิดขึ้นในข้อความของโปรแกรม การคอมไพล์จะหยุดลงและข้อความแสดงข้อผิดพลาดจะปรากฏขึ้นบนหน้าจอแสดงผล คำสั่งนี้ส่วนใหญ่จะใช้ในระหว่างขั้นตอนการดีบัก โปรดทราบว่าข้อความแสดงข้อผิดพลาดไม่จำเป็นต้องอยู่ในเครื่องหมายคำพูดคู่

คำสั่ง #line มีวัตถุประสงค์เพื่อเปลี่ยนค่าของตัวแปร _LINE_ และ _FILE_ ที่กำหนดไว้ในระบบการเขียนโปรแกรม C ตัวแปร _LINE_ ประกอบด้วยหมายเลขบรรทัดของโปรแกรมที่กำลังดำเนินการอยู่ ตัวระบุ _FILE_ เป็นตัวชี้ไปยังสตริงที่มีชื่อของโปรแกรมที่กำลังคอมไพล์ คำสั่ง #line เขียนดังนี้:

#หมายเลขบรรทัด "file_name"

ตัวเลขในที่นี้คือจำนวนเต็มบวกใดๆ ที่จะกำหนดให้กับตัวแปร _LINE_ ส่วน file_name เป็นพารามิเตอร์ทางเลือกที่จะแทนที่ค่าของ _FILE_

คำสั่ง #pragma ช่วยให้คุณสามารถส่งคำสั่งบางอย่างไปยังคอมไพเลอร์ได้ เช่น เส้น

บ่งชี้ว่าโปรแกรม C มีสตริงภาษาแอสเซมบลี ตัวอย่างเช่น:

ลองดูที่ตัวระบุส่วนกลางหรือชื่อมาโคร (ชื่อคำจำกัดความของมาโคร) มีการกำหนดชื่อดังกล่าวไว้ห้าชื่อ: _LINE_, _FILE_, _DATE_, _TIME_, _STDC_ สองรายการ (_LINE_ และ _FILE_) ได้รับการอธิบายไว้ข้างต้นแล้ว ตัวระบุ _DATE_ ระบุสตริงที่เก็บวันที่ที่ไฟล์ต้นฉบับถูกแปลเป็นออบเจ็กต์โค้ด ตัวระบุ _TIME_ ระบุสตริงที่เก็บเวลาที่ไฟล์ต้นฉบับถูกแปลเป็นออบเจ็กต์โค้ด แมโคร _STDC_ มีค่าเป็น 1 ถ้ามีการใช้ชื่อแมโครที่กำหนดมาตรฐาน มิฉะนั้นตัวแปรนี้จะไม่ได้รับการกำหนด



มีคำถามหรือไม่?

แจ้งการพิมพ์ผิด

ข้อความที่จะส่งถึงบรรณาธิการของเรา: