เหตุใด C จึงครองตลาดซอฟต์แวร์ฝังตัว [ปิด]


14

เกือบทุกคนในตอนนี้จะกล่าวคำอวยพร:

ประสิทธิภาพ !

โอเคCอนุญาตให้เขียนรหัสนักกีฬาได้ แต่มีภาษาอื่นที่สามารถทำได้หลังจากทั้งหมด! และพลังการปรับให้เหมาะสมที่สุดของคอมไพเลอร์สมัยใหม่นั้นยอดเยี่ยม ไม่Cมีข้อได้เปรียบบางอย่างที่ไม่มีภาษาอื่น ๆ ที่มี? หรือไม่จำเป็นต้องมีเครื่องมือที่มีความยืดหยุ่นมากขึ้นในโดเมน?


1
FWIW, Arduino สามารถควบคุมได้ด้วย C #: arduino.cc/playground/Interfacing/Csharp
FrustratedWithFormsDesigner

@ เฟร็ด: ใช่ แต่นั่นเป็นตัวอย่างหนึ่งและคนส่วนใหญ่ที่สร้างอุปกรณ์กำลังใช้ Arduino
Ed S.

2
ที่เกี่ยวข้อง: stackoverflow.com/questions/1601893/…
Steve S

2
ดูเพิ่มเติมที่: stackoverflow.com/questions/812717/…
Steve Melnikoff

1
@ dan04 ในกรณีส่วนใหญ่นั่นไม่ใช่ปัญหา กลุ่มจำลอง 6DOF ที่ Texas Instruments Defense Systems และกลุ่มอิเล็กทรอนิกส์ได้ทำการทดลองเล็กน้อยในปี 1988 จนกระทั่งถึงตอนนั้นพวกเขาได้จำลองสถานการณ์ทั้งหมดใน FORTRAN พวกเขาพยายามเขียนหนึ่งใน PASCAL เพื่อดูว่ามันจะไม่ดี พวกเขาค้นพบว่า PASCAL ให้ผลงานเล็ก ๆ น้อย ๆ แก่พวกเขา แต่การเพิ่มความน่าเชื่อถือและความสะดวกในการดีบั๊กมากกว่าที่ทำไว้สำหรับมัน พวกเขาพบว่าการตรวจสอบที่แข็งแกร่งของ PASCAL นั้นเป็นสิ่งที่ดี (และใช่พวกเขากำลังทำอาร์เรย์)
John R. Strohm

คำตอบ:


41

เกือบทุกคนในตอนนี้จะกล่าวคำอวยพร:

ประสิทธิภาพ!

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

  1. เข้าถึง API ฮาร์ดแวร์ระดับต่ำได้โดยตรง
  2. คุณสามารถค้นหาคอมไพเลอร์ C สำหรับอุปกรณ์เหล่านี้ส่วนใหญ่ นี่ไม่ใช่ความจริงสำหรับภาษาระดับสูงใด ๆ ในประสบการณ์ของฉัน
  3. C (รันไทม์และไฟล์ปฏิบัติการที่คุณสร้างไว้) คือ "เล็ก" คุณไม่จำเป็นต้องโหลดสิ่งต่าง ๆ เข้าไปในระบบเพื่อให้โค้ดทำงาน
  4. API / ไดรเวอร์ฮาร์ดแวร์น่าจะเขียนใน C หรือ C ++

14
+1 ความพร้อมของคอมไพเลอร์ ย้อนกลับไปตอนที่เราเขียนทุกอย่างในชุดประกอบคอมไพเลอร์เจนคนแรกเป็นเทพเจ้า
Christopher Bibbs

8
+1 ฉันคิดว่าการใช้ทรัพยากรที่กำหนดไว้นั้นเป็นเหตุผลที่สำคัญที่สุด คุณมีหน่วยความจำไม่มากที่จะทำชุดเก็บขยะแฟนซีทุกชนิดในเครื่องล้างจาน
281377

4
+1 สำหรับ "การใช้ทรัพยากรที่กำหนด" บนระบบฝังตัวจำนวนมากข้อกำหนดนี้ยังห้ามการใช้การจัดสรรหน่วยความจำแบบไดนามิก ภาษาอื่น ๆ อีกมากมายพึ่งพาการจัดสรรหน่วยความจำแบบไดนามิกอย่างมาก (แม้กระทั่งแง่มุมที่เป็นประโยชน์มากมายของ C ++ ก็ต้องการหน่วยความจำแบบไดนามิก)
Michael Burr

2
ฉันจะเพิ่มสัญลักษณ์แสดงหัวข้อย่อยอีกหนึ่งซึ่งกลายเป็นสังคมมากกว่าเหตุผลทางเทคนิค - ฉันคิดว่านักพัฒนาซอฟต์แวร์ฝังตัวมีแนวโน้มที่จะอนุรักษ์และทนต่อการเปลี่ยนแปลงได้มากกว่านักพัฒนาคนอื่น ๆ สิ่งนี้อาจเป็นสิ่งที่ดีหรือไม่ดีทั้งนี้ขึ้นอยู่กับมุมมองของคุณ
Michael Burr

1
การพูดเป็นคนที่แต่งตัวประหลาดฉันเป็นนามธรรมที่ใหญ่โต พวกเขายอดเยี่ยมจนกว่าพวกเขาจะหยุดทำงานหรือทำอะไรตลก ๆ ซึ่งในกรณีนี้มันอาจปวดหัวมากในการแก้ไขข้อบกพร่อง นั่นไม่ใช่สิ่งที่ฉันต้องการในระบบระดับต่ำ
Ed S.

18

C ได้รับการออกแบบมาเพื่อจำลอง CPU เนื่องจาก C ถูกสร้างขึ้นเพื่อให้ Unix พกพาข้ามแพลตฟอร์มแทนที่จะเป็นเพียงการเขียนภาษาแอสเซมบลี

นี่หมายความว่าโปรแกรม C ทำงานได้ดีเป็นภาษาโปรแกรมสำหรับโปรแกรมที่จำเป็นต้องมีระดับนามธรรมใกล้เคียงกับ CPU จริงซึ่งเป็นกรณีของฮาร์ดแวร์ฝังตัว

หมายเหตุ: C ได้รับการออกแบบรอบปี 1970 และซีพียูนั้นง่ายกว่า


3
+1: นี่คือเหตุผลที่แน่นอน บางทีคนอาจพยายามออกแบบภาษาระดับสูงรุ่นใหม่ที่จับคุณสมบัติของโปรเซสเซอร์ที่ทันสมัย ​​แต่ไม่มีใครออกแบบภาษาสำหรับสิ่งที่ติดอยู่
Ken Bloom

2
@vines, C เป็นภาษาขนาดเล็กที่มีไลบรารีรันไทม์ขนาดใหญ่ ทั้งหมดนี้สามารถทำได้ในไลบรารีรันไทม์ มันจะไม่โอนย้ายโดยอัตโนมัติไปยังไลบรารี C มาตรฐานดังนั้นจึงเป็นแพลตฟอร์มเฉพาะ

3
+1 C ถูกสร้างขึ้นสำหรับและเริ่มใช้กับ PDP-7 ซึ่งมีขนาดสูงสุด 64kilowords คำ 18 บิต ภาษา "สมัยใหม่" อีกหลายภาษามีความยากในการปรับพื้นที่ให้เหมาะสม โดยเฉพาะอย่างยิ่งสำหรับการเขียนระบบปฏิบัติการเช่น Unix
greyfade

2
@greyfade: ไม่เป็นเช่นนั้น UNIX มาจาก PDP-7 แต่ C ทำไม่ได้ เพื่ออ้างอิงจากคำนำไปสู่ภาษาการเขียนโปรแกรม C : "C ได้รับการออกแบบและนำไปใช้กับระบบปฏิบัติการ UNIX ใน DEC PDP-11 โดย Dennis Ritchie"
Jerry Coffin

1
@vines: แม้ว่าจะมีเหตุผลที่จะพิจารณาการสนับสนุนโดยตรงสำหรับเธรดในภาษา (cf, Concurrent C) จุดสำคัญของแคชก็คือมันทำให้สิ่งต่าง ๆ เร็วขึ้นโดยไม่ต้องมีการแทรกแซงในส่วนของโปรแกรมเมอร์หรือภาษา
Jerry Coffin

11

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

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


3
"ทั้ง Java และ C / C ++" - ฉันหวังว่าคุณหมายถึง "ทั้งสาม: Java C และ C ++" เนื่องจาก C และ C ++ เป็นภาษาที่แตกต่างกัน
BЈовић

1
@ BЈовић: ตอบหลายปีต่อมาเพื่อยืนยันว่าใช่ว่าฉันหมายถึงทั้งสาม ฉันใช้ทั้งคำจำกัดความนี้: "ใช้เป็นคำฟังก์ชันเพื่อระบุและเน้นถึงการรวมของสองอย่างหรือมากกว่านั้น" (สองอย่างหรือมากกว่านั้น) :-)
celebdor

10

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


1
มันไม่เคยเกิดขึ้นจริง ๆ หรือว่าคุณต้องการฟังก์ชั่นนั้น ตัวอย่างเช่นเครื่องสถานะขนาดใหญ่ที่สร้างด้วยswitches นั้นน่ากลัวและเครื่องเดียวกันที่สร้างขึ้นด้วยลำดับชั้นของคลาสนั้นดีและบำรุงรักษาได้
เถาวัลย์

1
@vines - โดยปกติคุณมีชุดอินพุตที่กำหนดไว้เครื่องจักรของรัฐที่สร้างขึ้นบนสวิตช์ / ถ้าบันไดมีความชัดเจนและสามารถจัดทำเอกสารได้ดีกว่า heiracrchy of magic 'ที่อยู่เบื้องหลังการเรียก polymorphic
Martin Beckett

2
@ มาร์ติน: สำหรับคนที่มีประสบการณ์น้อยในการพัฒนา OO การเรียกโพลีมอร์ฟิคไม่ใช่ "เวทมนต์" หรือ "เบื้องหลัง" และความคิดที่ว่าสวิตช์ขนาดใหญ่ / หากคำสั่งนั้นชัดเจนมากขึ้น
Michael Borgwardt

3
ค้นหาสิ่งที่เกิดขึ้นเมื่อ pin27 สูงขึ้น ตัวเลือกที่ 1 ค้นหา "case PIN27:" ตัวเลือกที่ 2 ติดตามตัววนซ้ำบนแผนที่ของ functors เพื่อค้นหาว่าใครจะถูกเรียกหาวัตถุ PIN ที่กำหนดให้ pin 27 เมื่อรันไทม์ ปัญหาที่มีรหัส OO จำนวนมากคือวิธีเดียวที่จะอ่านได้ก็คือการรันเป็นหลัก บนแพลตฟอร์มที่ไม่มีการดีบักรันไทม์หรือแม้แต่คอนโซลที่หมายถึงการติดตามโค้ดบนกระดาษหรือในหัวของคุณ
Martin Beckett

2
แทนเจนต์เล็กน้อยสำหรับการสนทนานี้มีเหตุผลตรรกะของบันได (รุ่นดั้งเดิมยิ่งกว่าswitchเดิมที่คุณสามารถพูดได้) ยังคงใช้ในแอปพลิเคชันแบบฝังหลายตัว ง่ายต่อการตรวจแก้จุดบกพร่องและง่ายต่อการตรวจสอบ
geekosaur

9

ดังที่ได้กล่าวไว้ในคำตอบอื่น ๆ C ได้รับการพัฒนาในต้นปี 1970 เพื่อแทนที่ภาษาแอสเซมบลีบนสถาปัตยกรรมมินิคอมพิวเตอร์ ก่อนหน้านั้นคอมพิวเตอร์เหล่านี้มักมีราคาหลายหมื่นดอลลาร์รวมถึงหน่วยความจำและอุปกรณ์ต่อพ่วง

ทุกวันนี้คุณสามารถรับพลังงานคอมพิวเตอร์ที่เท่ากันหรือมากกว่าด้วยไมโครคอนโทรลเลอร์ 16 บิตแบบฝังที่ราคาสี่ดอลลาร์หรือน้อยกว่าในปริมาณเดียว - รวมถึงตัวควบคุม RAM และ I / O ในตัว ไมโครคอนโทรลเลอร์แบบ 32 บิตอาจมีค่าใช้จ่ายหนึ่งดอลลาร์หรือมากกว่านั้น

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

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


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

3
@ David Thornley, เห็นด้วยอย่างสมบูรณ์, นั่นเป็นเหตุผลว่าทำไมฉันถึงมีโครงการไปด้วย 8, 16, และ 32-bit micros ในเวลาเดียวกันสำหรับลูกค้าที่แตกต่างกัน (การใช้พลังงานเป็นอีกเหตุผลหนึ่งสำหรับการใช้อุปกรณ์ขนาดเล็ก)
tcrosley

1
ราคาถูกกำหนดโดยหน่วยประมวลผลน้อยกว่าจำนวนพิน บอร์ดมีราคาแพงกว่าชิปมาก
Yttrill

7

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

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

  2. ประสิทธิภาพ. ใช่คุณพูดมัน ประสิทธิภาพยังคงเป็นราชาและอยู่ในสภาพแวดล้อมที่รูทีนหลักมักจะเขียนในแอสเซมเบลอร์หรือปรับให้เหมาะสมอย่างน้อยที่สุดใน c โดยอ้างอิงไปยังเอาต์พุตแอสเซมบลี บ่อยครั้งที่เป้าหมายแบบฝังจะมีราคาต่ำมากและมีความทรงจำน้อยมากและไม่กี่ mips

  3. ขนาด. C ++ มีแนวโน้มที่จะใหญ่ขึ้น แน่นอนว่าทุกอย่างที่ใช้ STL จะใหญ่ขึ้น โดยทั่วไปทั้งในแง่ของขนาดโปรแกรมและหน่วยความจำรอยเท้า

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


11
ดูสิมันเป็น # 3 ที่ดูเหมือนจะเป็นหนึ่งในตำนานที่แพร่หลายที่สุดเกี่ยวกับ C ++ เขียนคอนเทนเนอร์แบบปลอดภัยสำหรับ 5 ประเภทที่แตกต่างใน C และคุณจะทำให้เกิด "bloat" อย่างน้อยเท่ากับการใช้คอนเทนเนอร์ STL เดียวใน 5 ประเภทที่แตกต่างกัน โปรแกรมเมอร์ C หลีกเลี่ยงสิ่งนี้โดยการเขียนคอนเทนเนอร์บนชนิดทึบแสง (void *) การเปรียบเทียบว่ากับเทมเพลต STL เป็นข้อผิดพลาดหมวดหมู่ ต้องยอมรับว่ามันเป็นหนึ่งใน "เหตุผล" ที่พบบ่อยที่สุดที่จะชอบ C.
Edward Strange

ฉันยอมรับว่าการทำซ้ำฟังก์ชั่นเต็มรูปแบบใน c คุณจะจบลงด้วยรอยเดียวกับ c ++ 'ข้อดี' ของ c คือมันช่วยให้คุณสามารถเลือกได้ ในโครงการสำคัญใด ๆ ที่ฉันต้องการใช้ c ++ แต่บางครั้งฮาร์ดแวร์เป้าหมายถูก จำกัด ให้อยู่ในจุดที่ไม่สามารถใช้งานได้จริง ต้องบอกว่า # 1 เป็นเหตุผลหลักในประสบการณ์ของฉัน
ลุคเกรแฮม

6

สำหรับระบบฝังตัวสิ่งที่ยิ่งใหญ่คือประสิทธิภาพผลการดำเนินงานแต่อย่างที่คุณพูดทำไม C และไม่ใช่ภาษานักแสดงคนอื่น?

มีหลายคนที่กล่าวถึงความพร้อมใช้งานของคอมไพเลอร์แต่ไม่มีใครได้กล่าวถึงความพร้อมใช้งานของนักพัฒนาความพร้อมของนักพัฒนานักพัฒนาจำนวนมากรู้จัก C มากกว่าแล้วพูดว่า OCaml

นี่คือสามยักษ์ใหญ่


6

ซอฟต์แวร์ฝังตัวแตกต่างกันมาก

ในแอพเดสก์ท็อปนามธรรมและไลบรารีช่วยให้คุณประหยัดเวลาได้มาก คุณมีความหรูหราในการขว้างปาคู่อีกเมกะไบต์หรือ RAM กิกะไบต์หรือมีแกน CPU 2 + GHz 64 บิตที่มีปัญหาและคนอื่น (ผู้ใช้) กำลังจ่ายค่าฮาร์ดแวร์นั้น คุณอาจไม่ทราบว่าระบบจะใช้แอพใด

ในโครงการฝังตัวทรัพยากรมักจะมีข้อ จำกัด มาก ในโครงการหนึ่งที่ฉันทำงานด้วย (โปรเซสเซอร์ซีรีย์ PIC 17X) ฮาร์ดแวร์มีหน่วยความจำโปรแกรม 2KK, สแต็ก (ในฮาร์ดแวร์) 8 ระดับและ 192 ไบต์ (<0.2kB) ของ RAM หมุด I / O ที่แตกต่างกันมีความสามารถที่แตกต่างกันและคุณกำหนดค่าฮาร์ดแวร์ตามต้องการโดยการเขียนลงทะเบียนฮาร์ดแวร์ การดีบักเกี่ยวข้องกับออสซิลโลสโคปและลอจิกตัววิเคราะห์

ในที่ฝังตัว abstractions มักจะเข้าหาและจัดการทรัพยากร (และราคา) ที่คุณไม่มี เช่นระบบฝังตัวส่วนใหญ่ไม่มีระบบไฟล์ เตาอบไมโครเวฟเป็นระบบฝังตัว ตัวควบคุมเครื่องยนต์ของรถยนต์ แปรงสีฟันไฟฟ้าบางตัว หูฟังตัดเสียงรบกวนบางตัว

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

Abstractions และเบื้องหลัง 'เวทย์มนตร์' (เช่นนักเก็บขยะ) เหมาะสำหรับแอปบนเดสก์ท็อป นักสะสมขยะช่วยให้คุณประหยัดเวลาได้มากในการลดการรั่วไหลของหน่วยความจำเมื่อหน่วยความจำถูก / สามารถจัดสรรแบบไดนามิกได้

อย่างไรก็ตามในโลกแห่งการฝังตัวแบบเรียลไทม์เราจำเป็นต้องรู้และควบคุมว่าจะใช้เวลานานเท่าใดบางครั้งอาจลดลงเป็นนาโนวินาที ตัวอย่างง่ายๆอย่างหนึ่ง: เมื่อทำการลดแสงไฟ LED ของซอฟต์แวร์โดยการควบคุมรอบการทำงาน (CPU มีเพียงการควบคุมเปิด / ปิดของ LED เท่านั้น) มันไม่ตกลงสำหรับโปรเซสเซอร์ที่จะออกและทำเช่นการเก็บขยะ 100ms เพราะหน้าจอจะมองเห็นได้ชัดเจน สว่างหรือออกไปข้างนอก

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


นั่นเป็นเรื่องจริงตามที่ไม่เกี่ยวข้องกับ C - ปัญหาที่คุณอ้างถึงเป็นเพียงพฤติกรรมของ GC ... C ++ ไม่มี GCs ใด ๆ และรู้อะไรไหม ผมเองใช้มันเพราะของสายการแสดงความคิดเห็นและประเภทความปลอดภัยที่เข้มงวด =)
เถา
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.