หากตัวเลขมีขนาดใหญ่เกินไปมันจะกระจายไปยังตำแหน่งหน่วยความจำถัดไปหรือไม่


30

ฉันได้รับการตรวจสอบการเขียนโปรแกรม C และมีเพียงสองสามสิ่งที่รบกวนฉัน

ลองทำโค้ดนี้กันตัวอย่าง:

int myArray[5] = {1, 2, 2147483648, 4, 5};
int* ptr = myArray;
int i;
for(i=0; i<5; i++, ptr++)
    printf("\n Element %d holds %d at address %p", i, myArray[i], ptr);

ฉันรู้ว่า int สามารถมีค่าสูงสุดเป็นบวก 2,147,483,647 ดังนั้นเมื่อทำอย่างนั้นมันจะ "ล้น" ไปยังที่อยู่หน่วยความจำถัดไปซึ่งทำให้องค์ประกอบ 2 ปรากฏเป็น "-2147483648" ที่ที่อยู่นั้นหรือไม่ แต่นั่นก็ไม่สมเหตุสมผลเพราะในผลลัพธ์มันยังบอกว่าที่อยู่ถัดไปเก็บค่า 4 จากนั้น 5 ถ้าจำนวนที่หกไปยังที่อยู่ถัดไปนั่นจะไม่เปลี่ยนค่าที่เก็บไว้ที่ที่อยู่นั้น ?

ฉันจำรางจากการเขียนโปรแกรมใน MIPS Assembly และดูค่าการเปลี่ยนแปลงที่อยู่ในระหว่างขั้นตอนของโปรแกรมทีละขั้นตอนว่าค่าที่กำหนดให้กับที่อยู่เหล่านั้นจะเปลี่ยนไป

นอกจากว่าฉันจำไม่ถูกต้องแล้วนี่เป็นคำถามอื่น: ถ้าจำนวนที่กำหนดให้กับที่อยู่เฉพาะมีขนาดใหญ่กว่าประเภท (เช่นใน myArray [2]) แล้วมันจะไม่ส่งผลกระทบต่อค่าที่เก็บไว้ที่อยู่ต่อไป?

ตัวอย่าง: เรามี int myNum = 4 พันล้านที่ที่อยู่ 0x10010000 แน่นอน myNum ไม่สามารถจัดเก็บ 4 พันล้านดังนั้นจึงปรากฏเป็นจำนวนลบที่อยู่นั้น แม้ว่าจะไม่สามารถจัดเก็บจำนวนมากนี้ได้ แต่ก็ไม่มีผลต่อค่าที่เก็บไว้ในที่อยู่ถัดไปของ 0x10010004 แก้ไข?

ที่อยู่หน่วยความจำมีพื้นที่เพียงพอที่จะเก็บขนาดตัวเลข / ตัวอักษรและถ้าขนาดมีขนาดเกินขีด จำกัด ก็จะถูกนำเสนอแตกต่างกัน (เช่นพยายามเก็บ 4 พันล้านไว้ใน int แต่จะปรากฏเป็นตัวเลขติดลบ) และ ดังนั้นจึงไม่มีผลต่อตัวเลข / ตัวอักษรที่เก็บไว้ในที่อยู่ถัดไป

ขออภัยถ้าฉันลงน้ำ ฉันมีสมองที่ใหญ่ผายลมตลอดทั้งวันจากนี้


10
คุณอาจสับสนกับการใช้สายอักขระมากเกินไป
Robbie Dee

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

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

1
เขียนแบบทดสอบอย่างง่ายฉันไม่ใช่โปรแกรมเมอร์ C แต่มีบางอย่างตามมาint c = INT.MAXINT; c+=1;และดูว่าเกิดอะไรขึ้นกับค
JonH

2
@JonH: ปัญหาคือมากเกินไปในพฤติกรรมที่ไม่ได้กำหนด คอมไพเลอร์ AC อาจตรวจพบโค้ดนั้นและอนุมานได้ว่าเป็นโค้ดที่ไม่สามารถเข้าถึงได้เพราะมันโอเวอร์โฟลว์อย่างไม่มีเงื่อนไข เนื่องจากรหัสที่เข้าไม่ถึงนั้นไม่สำคัญจึงสามารถกำจัดได้ ผลลัพธ์สุดท้าย: ไม่มีโค้ดเหลืออยู่
MSalters

คำตอบ:


48

ไม่มันไม่ ใน C ตัวแปรมีชุดที่อยู่หน่วยความจำคงที่ที่จะทำงานด้วย ถ้าคุณกำลังทำงานในระบบที่มี 4 ไบต์intsและคุณตั้งค่าintตัวแปร2,147,483,647แล้วเพิ่มตัวแปรมักจะมี1 -2147483648(ในระบบส่วนใหญ่ลักษณะการทำงานนั้นไม่ได้กำหนดจริง ๆ ) จะไม่มีการแก้ไขตำแหน่งหน่วยความจำอื่น

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

มองในทางที่บิตถ้าชนิดเท่านั้นที่สามารถเก็บ 8 บิตและคุณพยายามที่จะบังคับให้ค่า1010101010101เป็นมันมีกรณี, คุณจะจบลงด้วยด้านล่าง 8 01010101บิตหรือ

ในตัวอย่างของคุณโดยไม่คำนึงถึงสิ่งที่คุณทำไปmyArray[2], myArray[3]จะมี '4' ไม่มี "การหกล้น" คุณกำลังพยายามใส่บางสิ่งที่มีขนาดเกิน 4 ไบต์มันจะทำทุกอย่างให้จบลงโดยปล่อยให้เหลือ 4 ไบต์ด้านล่าง -2147483648ในระบบส่วนใหญ่นี้จะทำให้

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


52
หากคุณกำลังทำงานกับระบบที่มี 4 ไบต์ ints และคุณตั้งค่าตัวแปร int เป็น 2,147,483,647 แล้วเพิ่ม 1 ตัวแปรจะมี -2147483648 => ไม่มันเป็นพฤติกรรมที่ไม่ได้กำหนดดังนั้นมันอาจวนรอบหรืออาจทำอย่างอื่นโดยสิ้นเชิง ฉันเคยเห็นคอมไพเลอร์ปรับการตรวจสอบตามการขาดล้นและมีลูปไม่สิ้นสุดเช่น ...
Matthieu M.

ขออภัยใช่คุณถูกต้อง ฉันควรจะเพิ่ม "ปกติ" ในนั้น
Gort the Robot

@ MatthieuM จากมุมมองของภาษานั่นเป็นเรื่องจริง ในแง่ของการดำเนินการในระบบที่กำหนดซึ่งเป็นสิ่งที่เรากำลังพูดถึงที่นี่มันไร้สาระแน่นอน
ฮอบส์

@ ฮอบส์: ปัญหาคือเมื่อคอมไพเลอร์ทำการหยุดโปรแกรมเนื่องจากไม่ได้กำหนดพฤติกรรมจริง ๆ แล้วการรันโปรแกรมจะทำให้เกิดพฤติกรรมที่ไม่คาดคิดซึ่งเทียบได้กับการเขียนทับหน่วยความจำ
Matthieu M.

24

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

อย่างไรก็ตามล้นจำนวนเต็มไม่ได้ลงนามมีการกำหนดอย่างดี มันจะห่อโมดูโล UINT_MAX + 1 หน่วยความจำที่ไม่ได้อยู่ในตัวแปรของคุณจะไม่ได้รับผลกระทบ

ดูเพิ่มเติมที่https://stackoverflow.com/q/18195715/951890


การโอเวอร์โฟลว์จำนวนเต็มแบบมีการกำหนดเช่นเดียวกับการโอเวอร์โฟลว์จำนวนเต็มแบบไม่ได้ลงนาม ถ้าคำนั้นมี $ N $ บิตขอบเขตด้านบนของจำนวนเต็มล้นที่ลงนามคือ $ 2 ^ {N-1} -1 $$ (ซึ่งมันล้อมรอบถึง $ -2 ^ {N-1} $) ในขณะที่ ขอบเขตสูงสุดของการล้นจำนวนเต็มที่ไม่ได้ลงนามคือ $ 2 ^ N - 1 $$ (ซึ่งจะมีค่าประมาณ $ 0 $) กลไกเดียวกันสำหรับการบวกและการลบขนาดเดียวกันของช่วงของตัวเลข ($ 2 ^ N $) ที่สามารถแสดงได้ เป็นเพียงขอบเขตที่แตกต่างกันของการล้น
เบิร์ตบริสโตจอห์นสัน

1
@ robertbristow-johnson: ไม่เป็นไปตามมาตรฐาน C
Vaughn Cato

ดีบางครั้งมาตรฐานผิดปกติ มองที่การอ้างอิง SO นั้นมีหนึ่งความคิดเห็นที่กระทบโดยตรง: "หมายเหตุสำคัญที่นี่แม้ว่าจะไม่มีสถาปัตยกรรมในโลกสมัยใหม่ที่ใช้สิ่งอื่นใดนอกจากส่วนเติมเต็มของเลข 2 ที่ลงนามว่ามาตรฐานภาษายังอนุญาตให้นำไปใช้ ในเช่น PDP-1 เป็นสิ่งประดิษฐ์ทางประวัติศาสตร์อันบริสุทธิ์ - Andy Ross 12 ส.ค. 13 เวลา 20:12 น.
เบิร์ตบริสโต - จอห์นสัน

ฉันคิดว่ามันไม่ได้อยู่ในมาตรฐาน C intแต่ผมคิดว่าอาจจะมีการดำเนินการที่เลขคณิตไบนารีปกติไม่ได้ใช้สำหรับ ฉัน s'pose พวกเขาสามารถใช้รหัสสีเทาหรือBCDหรือEBCDIC dunno ทำไมทุกคนจะออกแบบฮาร์ดแวร์เพื่อทำเลขคณิตด้วยรหัสสีเทาหรือ EBCDIC แต่แล้วอีกครั้งฉันไม่รู้ว่าทำไมทุกคนจะทำunsignedกับไบนารีและลงนามintกับสิ่งอื่นที่ไม่ใช่ส่วนเติมเต็มของ 2
robert bristow-johnson

14

ดังนั้นมีสองสิ่งที่นี่:

  • ระดับภาษา: ความหมายของ C คืออะไร
  • ระดับเครื่อง: ความหมายของแอสเซมบลี / CPU ที่คุณใช้คืออะไร

ในระดับภาษา:

ใน C:

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

สำหรับผู้ที่ต้องการตัวอย่าง "อะไร" ฉันได้เห็น:

for (int i = 0; i >= 0; i++) {
    ...
}

เปลี่ยนเป็น:

for (int i = 0; true; i++) {
    ...
}

และใช่นี่เป็นการเปลี่ยนแปลงที่ถูกต้องตามกฎหมาย

หมายความว่ามีความเสี่ยงที่อาจเกิดขึ้นได้จากการเขียนทับหน่วยความจำในโอเวอร์โฟลว์เนื่องจากการเปลี่ยนแปลงคอมไพเลอร์แปลก ๆ

หมายเหตุ: ใน Clang หรือ gcc ใช้-fsanitize=undefinedใน Debug เพื่อเปิดใช้งานUndefined Behavior Sanitizerซึ่งจะยกเลิกการ underflow / overflow ของจำนวนเต็มที่ลงนามแล้ว

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

หมายเหตุ: ใน Clang หรือ gcc ใช้-fsanitize=addressใน Debug เพื่อเปิดใช้งานAddress Sanitizerซึ่งจะยกเลิกการเข้าถึงนอกขอบเขต


ที่ระดับเครื่อง :

ขึ้นอยู่กับคำแนะนำการประกอบและ CPU ที่คุณใช้จริง ๆ :

  • ใน x86, ADDจะใช้ 2 ส่วนเติมเต็มสำหรับ overflow / underflow และตั้งค่าของ (Overflow Flag)
  • บน Mill CPU ในอนาคตจะมีโหมดโอเวอร์โฟลแตกต่างกัน 4 โหมดสำหรับAdd:
    • Modulo: 2-modulo
    • Trap: กับดักถูกสร้างขึ้น, หยุดการคำนวณ
    • satate: มูลค่าจะติดอยู่ที่ค่าต่ำสุดสำหรับ underflow หรือสูงสุดที่ overflow
    • ความกว้างสองเท่า: ผลลัพธ์ถูกสร้างขึ้นในการลงทะเบียนความกว้างสองเท่า

โปรดทราบว่าไม่ว่าจะเกิดอะไรขึ้นในการลงทะเบียนหรือหน่วยความจำ CPU จะไม่เขียนทับหน่วยความจำในโอเวอร์โฟลว์


มีการเซ็นชื่อสามโหมดล่าสุดหรือไม่ (ไม่สำคัญสำหรับคนแรกเนื่องจากเป็น 2 ส่วนประกอบ)
Deduplicator

1
@Dupuplicator: จากข้อมูลเบื้องต้นเกี่ยวกับโมเดลการเขียนโปรแกรม Mill CPUมี opcodes ที่แตกต่างกันสำหรับการเพิ่มที่ลงนามและการเพิ่มที่ไม่ได้ลงชื่อ ฉันคาดหวังว่า opcodes ทั้งสองจะรองรับ 4 โหมด (และสามารถทำงานกับบิตไวด์และสเกลาร์ / เวกเตอร์ต่างๆ) จากนั้นอีกครั้งมันเป็นฮาร์ดแวร์ไอสำหรับตอนนี้;)
แมททิวเอ็ม

4

หากต้องการคำตอบเพิ่มเติมของ StevenBurnap เหตุผลนี้เกิดขึ้นเพราะคอมพิวเตอร์ทำงานในระดับเครื่อง

อาร์เรย์ของคุณถูกเก็บไว้ในหน่วยความจำ (เช่นใน RAM) เมื่อมีการดำเนินการทางคณิตศาสตร์ค่าในหน่วยความจำจะถูกคัดลอกลงในอินพุตของวงจรที่ดำเนินการทางคณิตศาสตร์ (ALU: หน่วยคำนวณเลขคณิตตรรกะ ) จากนั้นการดำเนินการจะดำเนินการกับข้อมูลในการลงทะเบียนอินพุตทำให้เกิดผลลัพธ์ ในเอาต์พุตรีจิสเตอร์ ผลลัพธ์นี้จะถูกคัดลอกกลับไปยังหน่วยความจำตามที่อยู่ที่ถูกต้องในหน่วยความจำทำให้พื้นที่อื่น ๆ ของหน่วยความจำไม่ถูกแตะต้อง


4

ขั้นแรก (สมมติว่าเป็นมาตรฐาน C99) คุณอาจต้องการรวม<stdint.h>ส่วนหัวมาตรฐานและใช้บางประเภทที่กำหนดไว้โดยเฉพาะอย่างยิ่งint32_tซึ่งเป็นจำนวนเต็ม 32 บิตที่เซ็นชื่อuint64_tอย่างแน่นอนหรือซึ่งเป็นจำนวนเต็ม 64 บิตที่ไม่ได้ลงชื่อและอื่น ๆ คุณอาจต้องการใช้ประเภทเช่นint_fast16_tเหตุผลด้านประสิทธิภาพ

อ่านคำตอบของผู้อื่นอธิบายว่าเลขคณิตที่ไม่ได้ลงนามนั้นจะไม่รั่วไหล (หรือล้น) ไปยังตำแหน่งหน่วยความจำที่อยู่ติดกัน ระวังพฤติกรรมที่ไม่ได้กำหนดในการโอเวอร์โฟลว์ที่ลงนามแล้ว

แล้วถ้าคุณต้องคำนวณว่าตัวเลขจำนวนเต็มขนาดใหญ่ (เช่นคุณต้องการที่จะคำนวณปัจจัย 1000 กับตัวเลข 2568 ในทศนิยม), คุณต้องการ bigints aka พลหมายเลขแม่นยำ (หรือ bignums) อัลกอริทึมสำหรับการคำนวณเลขบิ๊กคินต์ที่มีประสิทธิภาพนั้นฉลาดและมักต้องใช้คำสั่งเครื่องพิเศษ (เช่นบางคำเพิ่มด้วยการพกพาหากโปรเซสเซอร์ของคุณมี) ดังนั้นฉันขอแนะนำอย่างยิ่งในกรณีนี้ให้ใช้ไลบรารี bigint ที่มีอยู่เช่นGMPlib

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