ประเภทความกว้างของตัวแปรถูกแทนที่ด้วยประเภทคงที่ในโมเดิร์นซีหรือไม่


21

ฉันมาข้ามจุดที่น่าสนใจวันนี้ในการตรวจสอบมากกว่าเมื่อรหัสตรวจสอบ @Veedrac recommened ในคำตอบนี้ว่าประเภทขนาดตัวแปร (เช่นintและlong) ถูกแทนที่ด้วยประเภทขนาดคงที่เหมือนและuint64_t uint32_tการอ้างอิงจากความคิดเห็นของคำตอบนั้น:

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

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

ฉันคิดว่าความตั้งใจในการออกแบบนั้น แต่เดิมนั้นแต่ละประเภทอื่น ๆ นอกเหนือจาก int เป็นสิ่งที่เล็กที่สุดที่สามารถรองรับตัวเลขขนาดต่างๆและ int นั้นเป็นขนาด "วัตถุประสงค์ทั่วไป" ที่ใช้งานได้จริงที่สุดที่สามารถจัดการ +/- 32767

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

ฉันติดอยู่ในยุค 70 หรือมีเหตุผลintในการใช้งานในยุค C99 หรือไม่?


1
คนส่วนหนึ่งเลียนแบบคนอื่น ฉันเชื่อว่าส่วนใหญ่ของรหัส fixed-bit-type ถูกสร้างขึ้นมาอย่างไร้ที่ติ ไม่มีเหตุผลในการตั้งค่าขนาดไม่เป็นไม่ ฉันมีโค้ดที่สร้างขึ้นเป็นหลักบนแพลตฟอร์ม 16 บิต (MS-DOS และ Xenix 80s) ที่เพิ่งคอมไพล์และรันในวันนี้บน 64 ใด ๆ และประโยชน์ของคำขนาดใหม่และที่อยู่เพียงแค่รวบรวมมัน กล่าวคือการจัดลำดับข้อมูลการส่งออก / นำเข้าเป็นการออกแบบสถาปัตยกรรมที่สำคัญมากเพื่อให้สามารถพกพาได้
Luciano

1
ที่เกี่ยวข้อง: stackoverflow.com/questions/24444356/…
dan04

คำตอบ:


7

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

ลองพิจารณาตัวอย่างเช่น:

uint32_t upow(uint32_t n, uint32_t exponent)
{
  while(exponent--)
    n*=n;
  return n;
}

int32_t spow(int32_t n, uint32_t exponent)
{
  while(exponent--)
    n*=n;
  return n;
}

บนเครื่องที่intไม่สามารถเก็บ 4294967295 หรือสามารถเก็บ 18446744065119617025 ฟังก์ชันแรกจะถูกกำหนดสำหรับค่าทั้งหมดnและexponentและพฤติกรรมของมันจะไม่ได้รับผลกระทบจากขนาดของint; นอกจากนี้มาตรฐานจะไม่กำหนดว่าจะให้ผลการทำงานที่แตกต่างกันในเครื่องที่มีขนาดint ค่าใดค่าหนึ่งnและexponentจะทำให้เรียกใช้พฤติกรรมที่ไม่ได้กำหนดบนเครื่องที่ 4294967295 แทนได้intแต่ไม่ใช่ 18446744065119617025

ฟังก์ชั่นที่สองจะให้ผลแบบไม่ได้กำหนดพฤติกรรมสำหรับค่าบางอย่างของnและexponentบนเครื่องที่intไม่สามารถถือ 4611686014132420609 แต่จะให้ผลพฤติกรรมที่กำหนดไว้สำหรับค่าทั้งหมดของnและexponentในทุกเครื่องที่มันสามารถ (ข้อกำหนดสำหรับint32_tบ่งบอกถึงพฤติกรรมการห่อ มีขนาดเล็กกว่าint)

ในอดีตแม้ว่ามาตรฐานกล่าวว่าไม่มีอะไรเกี่ยวกับสิ่งที่คอมไพเลอร์ควรจะทำอย่างไรกับintล้นในupowคอมไพเลอร์จะมีผลต่อเนื่องพฤติกรรมเช่นเดียวกับถ้าintได้รับมากพอที่จะไม่ล้น น่าเสียดายที่คอมไพเลอร์ที่ใหม่กว่าบางตัวอาจพยายามที่จะ "เพิ่มประสิทธิภาพ" โปรแกรมโดยกำจัดพฤติกรรมที่ไม่ได้รับคำสั่งจากมาตรฐาน


3
ทุกคนที่เกิดขึ้นที่ต้องการใช้งานด้วยตนเองpowโปรดจำไว้ว่ารหัสนี้เป็นเพียงตัวอย่างและไม่รองรับexponent=0!
Mark Hurd

1
ฉันคิดว่าคุณควรใช้โอเปอเรเตอร์การลดคำนำหน้าไม่ใช่ postfix ขณะนี้มันกำลังทำการคูณ 1 พิเศษเช่นexponent=1จะทำให้ n ถูกคูณด้วยตัวมันเองหนึ่งครั้งเนื่องจากการลดลงจะดำเนินการหลังจากการตรวจสอบหากการเพิ่มขึ้นจะทำก่อนการตรวจสอบ ie - exponent) จะไม่มีการคูณและจะส่งคืน n ตัวเอง
ALXGTV

2
@ MarkHurd: ฟังก์ชั่นตั้งชื่อได้ไม่ดีเนื่องจากสิ่งที่มันคำนวณจริงN^(2^exponent)แต่การคำนวณของรูปแบบN^(2^exponent)ที่มักจะใช้ในการคำนวณฟังก์ชั่นการยกกำลังและการยกกำลัง mod-4294967296 มีประโยชน์สำหรับสิ่งต่าง ๆ เช่นการคำนวณแฮชของการต่อสองบรรทัด hashes เป็นที่รู้จักกัน
supercat

1
@ALXGTV: ฟังก์ชั่นนี้มีความหมายว่าเป็นตัวอย่างของสิ่งที่คำนวณพลังงานที่เกี่ยวข้อง สิ่งที่คำนวณได้จริงคือ N ^ (2 ^ เลขชี้กำลัง) ซึ่งเป็นส่วนหนึ่งของการคำนวณอย่างมีประสิทธิภาพ N ^ เลขชี้กำลังและอาจล้มเหลวได้แม้ว่า N จะมีขนาดเล็ก (การคูณซ้ำของ a uint32_t31 จะไม่ให้ผลลัพธ์ UB แต่ประสิทธิภาพ วิธีในการคำนวณ 31 ^ N หมายถึงการคำนวณ 31 ^ (2 ^ N) ซึ่งจะ
Supercat

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

4

สำหรับค่าที่เกี่ยวข้องกับพอยน์เตอร์อย่างใกล้ชิด (และตามจำนวนหน่วยความจำที่กำหนดแอดเดรสได้) เช่นขนาดบัฟเฟอร์ดัชนีอาเรย์และ Windows ' lParamมันเหมาะสมที่จะมีประเภทจำนวนเต็มที่มีขนาดขึ้นอยู่กับสถาปัตยกรรม ดังนั้นประเภทขนาดตัวแปรยังคงมีประโยชน์ นี่คือเหตุผลที่เรามี typedefs size_t, ptrdiff_t, intptr_tฯลฯ พวกเขามีที่จะ typedefs เพราะไม่มีในตัว C จำนวนเต็มชนิดจำเป็นต้องเป็นตัวชี้ขนาด

ดังนั้นคำถามคือจริงๆไม่ว่าจะเป็นchar, short, int, longและlong longยังคงมีประโยชน์

IME ยังคงเป็นเรื่องปกติสำหรับโปรแกรม C และ C ++ ที่จะใช้intกับสิ่งต่างๆ และส่วนใหญ่เวลา (เช่นเมื่อตัวเลขของคุณอยู่ในช่วง± 32 767 และคุณไม่มีข้อกำหนดด้านประสิทธิภาพที่เข้มงวด) สิ่งนี้ใช้ได้ดี

แต่ถ้าคุณต้องการทำงานกับตัวเลขในช่วง 17-32 บิต (เช่นประชากรของเมืองใหญ่) คุณสามารถใช้intแต่นั่นเป็นการเข้ารหัสฮาร์ดโค้ดที่ต้องพึ่งพาแพลตฟอร์ม หากคุณต้องการปฏิบัติตามมาตรฐานอย่างเคร่งครัดคุณสามารถใช้longซึ่งรับประกันว่าจะมีอย่างน้อย 32 บิต

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

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

  • คอมไพเลอร์ I16L32 ของคุณให้ 32 บิตlongเพื่อหลีกเลี่ยงปัญหาการตัดทอนint
  • คอมไพเลอร์ I32L64 ของคุณจะให้ 32 บิตintหลีกเลี่ยงการสูญเสียหน่วยความจำของ long64
  • คอมไพเลอร์ I36L72 ของคุณให้คุณ 36 บิต int

OTOH สมมติว่าคุณไม่ต้องการตัวเลขขนาดใหญ่หรืออาร์เรย์ขนาดใหญ่ แต่คุณต้องการความเร็ว และintอาจจะเพียงพอขนาดใหญ่บนแพลตฟอร์มทั้งหมด แต่มันไม่จำเป็นต้องเป็นชนิดที่เร็วที่สุด: ระบบ 64 บิตมักจะยังคงมี int32 แต่คุณสามารถใช้int_fast16_tและได้รับพิมพ์“เร็วที่สุด” ไม่ว่าจะเป็นint, หรือlonglong long

<stdint.h>ดังนั้นมีกรณีการใช้งานในทางปฏิบัติสำหรับประเภทจาก ประเภทจำนวนเต็มมาตรฐานไม่ได้มีความหมายอะไรเลย โดยเฉพาะอย่างยิ่งlongซึ่งอาจเป็น 32 หรือ 64 บิตและอาจมีหรือไม่ใหญ่พอที่จะถือตัวชี้ขึ้นอยู่กับความต้องการของนักเขียนคอมไพเลอร์


ปัญหากับชนิดเช่นuint_least32_tว่าการปฏิสัมพันธ์กับประเภทอื่น ๆ uint32_tที่ระบุไว้มากยิ่งขึ้นอย่างอ่อนกว่า IMHO มาตรฐานควรกำหนดประเภทเช่นuwrap32_tและunum32_tด้วยความหมายว่าคอมไพเลอร์ใด ๆ ที่กำหนดประเภทuwrap32_tจะต้องส่งเสริมเป็นประเภทที่ไม่ได้ลงนามในกรณีพื้นฐานเป็นหลักเช่นเดียวกับที่มันจะได้รับการส่งเสริมถ้าintเป็น 32 บิตและคอมไพเลอร์ใด ๆ ที่กำหนดประเภทunum32_tต้องมั่นใจว่า การส่งเสริมการคำนวณทางคณิตศาสตร์ขั้นพื้นฐานจะแปลงเป็นประเภทที่มีการเซ็นชื่อซึ่งมีความสามารถในการเก็บค่าไว้
supercat

นอกจากนี้มาตรฐานยังสามารถกำหนดประเภทของการจัดเก็บและนามแฝงที่เข้ากันได้กับintN_tและuintN_tและพฤติกรรมที่กำหนดจะสอดคล้องกับintN_tและuintN_tแต่ที่จะให้คอมไพเลอร์มีอิสระบางอย่างในรหัสกรณีที่ได้รับมอบหมายค่านอกช่วง [อนุญาตความหมายคล้ายกับที่ อาจมีไว้สำหรับuint_least32_tแต่ไม่มีความไม่แน่นอนเช่นว่าการเพิ่มuint_least16_tและint32_tจะให้ผลลัพธ์ที่ลงนามหรือ usnigned
supercat
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.