main () method ทำงานอย่างไรใน C?


96

ฉันรู้ว่ามีสองลายเซ็นที่แตกต่างกันในการเขียนวิธีการหลัก -

int main()
{
   //Code
}

หรือสำหรับการจัดการอาร์กิวเมนต์บรรทัดคำสั่งเราเขียนเป็น -

int main(int argc, char * argv[])
{
   //code
}

ในC++ฉันรู้ว่าเราสามารถโอเวอร์โหลดเมธอดได้ แต่Cคอมไพเลอร์จัดการกับลายเซ็นของmainฟังก์ชันทั้งสองนี้อย่างไร


14
การโอเวอร์โหลดหมายถึงการมีสองวิธีที่มีชื่อเดียวกันในโปรแกรมเดียวกัน คุณสามารถมีmainวิธีเดียวในโปรแกรมเดียวในC(หรือในภาษาใดก็ได้ที่มีโครงสร้างดังกล่าว)
Kyle Strand

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

4
สำหรับการสนทนาอย่างลึกซึ้งเกี่ยวกับจุดเริ่มต้นของโปรแกรม - โดยเฉพาะอย่างยิ่งmain- ฉันขอแนะนำหนังสือคลาสสิกของ John R. Levines "Linkers & Loaders"
Andreas Spindler

1
ใน C รูปแบบแรกคือint main(void)ไม่ใช่int main()(แม้ว่าฉันจะไม่เคยเห็นคอมไพเลอร์ที่ปฏิเสธint main()แบบฟอร์มก็ตาม)
Keith Thompson

1
@harper: ()แบบฟอร์มนี้ล้าสมัยและไม่ชัดเจนว่าได้รับอนุญาตด้วยซ้ำmain(เว้นแต่การใช้งานจะจัดทำเป็นเอกสารเป็นรูปแบบที่อนุญาต) มาตรฐาน C (ดู 5.1.2.2.1 การเริ่มต้นโครงการ) ไม่พูดถึง()รูปแบบซึ่งไม่ได้ค่อนข้างเทียบเท่ากับ()รูปแบบ รายละเอียดยาวเกินไปสำหรับความคิดเห็นนี้
Keith Thompson

คำตอบ:


133

คุณลักษณะบางอย่างของภาษาซีเริ่มต้นจากการแฮ็กซึ่งเพิ่งเกิดขึ้นได้

ลายเซ็นหลายรายการสำหรับรายการอาร์กิวเมนต์หลักและความยาวผันแปรเป็นหนึ่งในคุณสมบัติเหล่านั้น

โปรแกรมเมอร์สังเกตว่าพวกเขาสามารถส่งผ่านอาร์กิวเมนต์พิเศษไปยังฟังก์ชันได้และไม่มีอะไรเลวร้ายเกิดขึ้นกับคอมไพเลอร์ที่กำหนด

เป็นกรณีนี้หากรูปแบบการโทรเป็นเช่นนั้น:

  1. ฟังก์ชันการโทรทำความสะอาดอาร์กิวเมนต์
  2. อาร์กิวเมนต์ซ้ายสุดอยู่ใกล้กับด้านบนสุดของสแต็กหรืออยู่ใกล้กับฐานของสแต็กเฟรมดังนั้นอาร์กิวเมนต์ปลอมจะไม่ทำให้การกำหนดแอดเดรสเป็นโมฆะ

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

 ;; pseudo-assembly-language
 ;; main(argc, argv, envp); call

 push envp  ;; rightmost argument
 push argv  ;; 
 push argc  ;; leftmost argument ends up on top of stack

 call main

 pop        ;; caller cleans up   
 pop
 pop

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

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

/* I'm adding envp to show that even a popular platform-specific variant
   can be handled. */
extern int main(int argc, char **argv, char **envp);

void __start(void)
{
  /* This is the real startup function for the executable.
     It performs a bunch of library initialization. */

  /* ... */

  /* And then: */
  exit(main(argc_from_somewhere, argv_from_somewhere, envp_from_somewhere));
}

กล่าวอีกนัยหนึ่งโมดูลเริ่มต้นนี้จะเรียกใช้อาร์กิวเมนต์หลักสามตัวเสมอ หาก main ไม่มีข้อโต้แย้งใด ๆ หรือเพียงอย่างเดียวint, char **มันก็ทำงานได้ดีเช่นเดียวกับว่ามันไม่มีข้อโต้แย้งใด ๆ เนื่องจากหลักการเรียก

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

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

กล่าวคือคุณเขียนสิ่งนี้:

int main(void)
{
   /* ... */
}

แต่เมื่อคอมไพเลอร์เห็นมันจะทำการแปลงรหัสเป็นหลักเพื่อให้ฟังก์ชันที่คอมไพล์มีลักษณะดังนี้:

int main(int __argc_ignore, char **__argv_ignore, char **__envp_ignore)
{
   /* ... */
}

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

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

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


34

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

สำหรับมาตรฐานค

สำหรับสภาพแวดล้อมที่โฮสต์ (ซึ่งเป็นสภาวะปกติ) มาตรฐาน C99 กล่าวว่า:

5.1.2.2.1 การเริ่มต้นโปรแกรม

mainฟังก์ชั่นที่เรียกว่าเมื่อเริ่มต้นโปรแกรมการตั้งชื่อ การใช้งานประกาศว่าไม่มีต้นแบบสำหรับฟังก์ชันนี้ จะต้องกำหนดด้วยประเภทการส่งคืนintและไม่มีพารามิเตอร์:

int main(void) { /* ... */ }

หรือด้วยพารามิเตอร์สองตัว (เรียกในที่นี้ว่าargcและargvแม้ว่าอาจจะใช้ชื่อใดก็ได้เนื่องจากเป็นชื่อเฉพาะของฟังก์ชันที่ประกาศไว้):

int main(int argc, char *argv[]) { /* ... */ }

หรือเทียบเท่า; 9)หรือในลักษณะอื่น ๆ ที่กำหนดไว้สำหรับการนำไปใช้งาน

9)ดังนั้นจึงintสามารถแทนที่ด้วยชื่อ typedef ที่กำหนดเป็นintหรือประเภทของargvสามารถเขียนเป็นchar **argvและอื่น ๆ

สำหรับ C ++ มาตรฐาน:

3.6.1 ฟังก์ชันหลัก [basic.start.main]

1 โปรแกรมจะต้องมีฟังก์ชันส่วนกลางที่เรียกว่า main ซึ่งเป็นจุดเริ่มต้นที่กำหนดของโปรแกรม [... ]

2 การนำไปใช้งานจะต้องไม่กำหนดฟังก์ชันหลักไว้ล่วงหน้า ฟังก์ชั่นนี้จะไม่มากเกินไป จะต้องมีประเภทผลตอบแทนเป็นประเภท int แต่มิฉะนั้นจะกำหนดประเภทการนำไปใช้งาน การใช้งานทั้งหมดจะต้องอนุญาตทั้งสองคำจำกัดความของ main:

int main() { /* ... */ }

และ

int main(int argc, char* argv[]) { /* ... */ }

มาตรฐาน C ++ ระบุอย่างชัดเจนว่า "It [the main function] จะต้องมี return type เป็น int แต่ไม่เช่นนั้นจะมีการกำหนดประเภทการนำไปใช้งาน" และต้องใช้ลายเซ็นสองแบบเดียวกันกับมาตรฐาน C

ในสภาพแวดล้อมที่โฮสต์ ( สภาพแวดล้อม AC ซึ่งรองรับไลบรารี C ด้วย) - ระบบปฏิบัติการเรียกmainใช้

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

#pragma startup [priority]
#pragma exit [priority]

โดยลำดับความสำคัญคือจำนวนอินทิกรัลที่เป็นทางเลือก

การเริ่มต้น Pragma เรียกใช้ฟังก์ชันก่อนที่ฟังก์ชัน main (Priority-wise) และ pragma exit จะเรียกใช้ฟังก์ชันหลังจากฟังก์ชันหลัก หากมีคำสั่งการเริ่มต้นมากกว่าหนึ่งคำสั่งลำดับความสำคัญจะตัดสินใจว่าจะดำเนินการใดก่อน


4
ฉันไม่คิดว่าคำตอบนี้จะตอบคำถามได้จริงว่าคอมไพเลอร์จัดการกับสถานการณ์อย่างไร คำตอบที่ได้รับจาก @Kaz ให้ข้อมูลเชิงลึกมากขึ้นในความคิดของฉัน
Tilman Vogel

4
ฉันคิดว่าคำตอบนี้ตอบคำถามได้ดีกว่าที่ @Kaz คำถามเดิมอยู่ภายใต้การแสดงผลที่ว่าตัวดำเนินการโอเวอร์โหลดกำลังเกิดขึ้นและคำตอบนี้แก้ไขได้โดยการแสดงว่าแทนที่จะใช้วิธีแก้ปัญหาการโอเวอร์โหลดคอมไพเลอร์ยอมรับลายเซ็นสองแบบที่แตกต่างกัน รายละเอียดคอมไพเลอร์น่าสนใจ แต่ไม่จำเป็นต้องตอบคำถาม
Waleed Khan

1
สำหรับสภาพแวดล้อมอิสระ ("ไม่โฮสต์") มีอะไรเกิดขึ้นมากกว่า #pragma เพียงบางส่วน มีการขัดจังหวะการรีเซ็ตจากฮาร์ดแวร์และนั่นคือจุดเริ่มต้นของโปรแกรม จากนั้นการตั้งค่าพื้นฐานทั้งหมดจะดำเนินการ: เซ็ตอัพสแต็กรีจิสเตอร์ MMU แมปหน่วยความจำเป็นต้นจากนั้นคัดลอกค่าเริ่มต้นจาก NVM ไปยังตัวแปรสตอเรจแบบคงที่ (.data segment) รวมทั้ง "zero-out" ทั้งหมด ตัวแปรหน่วยเก็บแบบคงที่ที่ควรตั้งค่าเป็นศูนย์ (.bss เซ็กเมนต์) ใน C ++ จะเรียกตัวสร้างของวัตถุที่มีระยะเวลาการจัดเก็บแบบคงที่ และเมื่อทุกอย่างเสร็จสิ้นแล้ว main จะถูกเรียก
Lundin

8

ไม่จำเป็นต้องมีการบรรทุกมากเกินไป ใช่มี 2 เวอร์ชัน แต่สามารถใช้ได้ครั้งเดียวเท่านั้น


5

นี่เป็นหนึ่งในความไม่สมมาตรที่แปลกประหลาดและกฎพิเศษของภาษา C และ C ++

ในความคิดของฉันมีอยู่ด้วยเหตุผลทางประวัติศาสตร์เท่านั้นและไม่มีตรรกะที่แท้จริงอยู่เบื้องหลัง โปรดทราบว่าmainมีความพิเศษด้วยเหตุผลอื่น ๆ (เช่นmainใน C ++ ไม่สามารถเรียกซ้ำได้และคุณไม่สามารถใช้ที่อยู่ได้และใน C99 / C ++ คุณได้รับอนุญาตให้ละเว้นreturnคำสั่งสุดท้าย)

โปรดทราบว่าแม้ใน C ++ จะไม่โอเวอร์โหลด ... ทั้งโปรแกรมมีรูปแบบแรกหรือมีรูปแบบที่สอง มันไม่มีทั้งสองอย่าง


คุณสามารถละเว้นreturnคำสั่งใน C ได้เช่นกัน (ตั้งแต่ C99)
dreamlax

ใน C คุณสามารถโทรmain()และรับที่อยู่ได้ C ++ ใช้ขีด จำกัด ที่ C ไม่มี
Jonathan Leffler

@JonathanLeffler: คุณพูดถูกคง สิ่งเดียวที่ตลกเกี่ยวกับหลักที่ฉันพบในข้อกำหนด C99 นอกเหนือจากความเป็นไปได้ที่จะละเว้นค่าที่ส่งคืนก็คือเนื่องจากมาตรฐานเป็นคำว่า IIUC คุณจึงไม่สามารถส่งผ่านค่าลบไปargcเมื่อทำการเรียกซ้ำ (5.1.2.2.1 ไม่ได้ระบุข้อ จำกัด ในargcและargvใช้กับการโทรครั้งแรกเท่านั้นmain)
6502

4

สิ่งที่ผิดปกติmainไม่ใช่ว่าสามารถกำหนดได้มากกว่าหนึ่งวิธีแต่สามารถกำหนดได้ด้วยวิธีใดวิธีหนึ่งเท่านั้น

mainเป็นฟังก์ชันที่ผู้ใช้กำหนดเอง การใช้งานไม่ได้ประกาศต้นแบบสำหรับมัน

สิ่งเดียวกันนี้เป็นจริงสำหรับfooหรือbarแต่คุณสามารถกำหนดฟังก์ชันด้วยชื่อเหล่านั้นได้ตามต้องการ

ความแตกต่างคือmainเรียกใช้โดยการนำไปใช้งาน (สภาพแวดล้อมรันไทม์) ไม่ใช่แค่รหัสของคุณเอง การใช้งานไม่ได้ จำกัด อยู่ที่ฟังก์ชัน C ธรรมดาที่เรียกใช้ความหมายดังนั้นจึงสามารถ (และต้อง) จัดการกับรูปแบบต่างๆได้เล็กน้อย - แต่ไม่จำเป็นต้องจัดการกับความเป็นไปได้มากมายที่ไม่มีที่สิ้นสุด int main(int argc, char *argv[])รูปแบบช่วยให้การอาร์กิวเมนต์บรรทัดคำสั่งและint main(void)ใน C หรือint main()ใน C ++ เป็นเพียงความสะดวกสบายสำหรับโปรแกรมง่ายๆที่ไม่จำเป็นต้องกระบวนการอาร์กิวเมนต์บรรทัดคำสั่ง

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

และเช่นเดียวกับหลาย ๆ อย่างใน C และ C ++ รายละเอียดส่วนใหญ่เป็นผลมาจากประวัติศาสตร์และการตัดสินใจโดยพลการของนักออกแบบภาษาและรุ่นก่อน ๆ

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


3

mainเป็นเพียงชื่อที่อยู่ที่เริ่มต้นตัดสินใจโดยลิงเกอร์ที่mainเป็นชื่อเริ่มต้น ชื่อฟังก์ชันทั้งหมดในโปรแกรมคือที่อยู่เริ่มต้นที่ฟังก์ชันเริ่มทำงาน

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


2

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

    int main(int argc, char * argv[])
    {
       //code
    }

โดยที่ตัวแปร argc เก็บจำนวนข้อมูลที่ส่งผ่านและ argv คืออาร์เรย์ของพอยน์เตอร์ไปยังถ่านที่ชี้ไปยังค่าที่ส่งผ่านจากคอนโซล มิฉะนั้นจะดีเสมอไป

    int main()
    {
       //Code
    }

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


2

ก่อนหน้านี้มีคำถามคล้าย ๆ กัน: ทำไมฟังก์ชันที่ไม่มีพารามิเตอร์ (เทียบกับนิยามฟังก์ชันจริง) จึงคอมไพล์

หนึ่งในคำตอบอันดับต้น ๆ คือ:

ในภาษา C func()หมายความว่าคุณสามารถผ่านได้อาร์กิวเมนต์จำนวนเท่าใดก็ได้ หากคุณไม่ต้องการโต้แย้งคุณต้องประกาศเป็นfunc(void)

ดังนั้นฉันเดาว่าmainมีการประกาศอย่างไร (หากคุณสามารถใช้คำว่า "ประกาศ" กับmain ) ในความเป็นจริงคุณสามารถเขียนสิ่งนี้:

int main(int only_one_argument) {
    // code
}

และจะยังคงคอมไพล์และรัน


1
สังเกตดีเยี่ยม! ดูเหมือนว่าผู้เชื่อมโยงค่อนข้างให้อภัยmainเนื่องจากยังไม่มีปัญหาที่ยังไม่ได้กล่าวถึง: ยิ่งมีข้อโต้แย้งมากขึ้นmain ! "Unix (แต่ไม่ใช่ Posix.1) และ Microsoft Windows" เพิ่มchar **envp(ฉันจำได้ว่า DOS อนุญาตเช่นกันใช่หรือไม่) และ Mac OS X และ Darwin ยังเพิ่มตัวชี้ถ่าน "ข้อมูลที่จัดทำโดยระบบปฏิบัติการ" อีกตัว wikipedia
usr2564301

0

คุณไม่จำเป็นต้องลบล้างสิ่งนี้เพราะจะใช้เพียงครั้งละหนึ่งฟังก์ชันหลักมี 2 เวอร์ชันที่แตกต่างกัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.