การเข้าถึงอาเรย์นอกขอบเขตนั้นไม่มีข้อผิดพลาดทำไม?


177

ฉันกำลังกำหนดค่าในโปรแกรม C ++ จากขอบเขตดังนี้:

#include <iostream>
using namespace std;
int main()
{
    int array[2];
    array[0] = 1;
    array[1] = 2;
    array[3] = 3;
    array[4] = 4;
    cout << array[3] << endl;
    cout << array[4] << endl;
    return 0;
}

โปรแกรมพิมพ์และ3 4มันไม่ควรจะเป็นไปได้ ฉันใช้ g ++ 4.3.3

นี่คือคำสั่งรวบรวมและเรียกใช้

$ g++ -W -Wall errorRange.cpp -o errorRange
$ ./errorRange
3
4

เมื่อกำหนดเท่านั้นarray[3000]=3000มันให้ฉันแบ่งส่วนความผิด

หาก gcc ไม่ตรวจสอบขอบเขตของอาร์เรย์ฉันจะแน่ใจได้อย่างไรว่าโปรแกรมของฉันถูกต้องเพราะจะนำไปสู่ปัญหาร้ายแรงบางอย่างในภายหลัง

ฉันแทนที่รหัสข้างต้นด้วย

vector<int> vint(2);
vint[0] = 0;
vint[1] = 1;
vint[2] = 2;
vint[5] = 5;
cout << vint[2] << endl;
cout << vint[5] << endl;

และอันนี้ก็ไม่มีข้อผิดพลาด


3
คำถามที่เกี่ยวข้อง: stackoverflow.com/questions/671703/…
TSomKes

16
แน่นอนว่าเป็นรหัสบั๊กกี้ แต่ก็สร้างพฤติกรรมที่ไม่ได้กำหนด ไม่ได้กำหนดหมายถึงมันอาจจะทำงานได้หรือไม่ก็ได้ ไม่มีการรับประกันความผิดพลาด
dmckee --- ผู้ดูแลอดีตลูกแมว

4
คุณสามารถมั่นใจได้ว่าโปรแกรมของคุณถูกต้องโดยไม่พลาดไปกับอาเรย์ดิบ โปรแกรมเมอร์ C ++ ควรใช้คลาสของคอนเทนเนอร์แทนยกเว้นในการเขียนโปรแกรมแบบฝัง / OS อ่านสิ่งนี้เพื่อเหตุผลในการบรรจุผู้ใช้ parashift.com/c++-faq-lite/containers.html
jkeys

8
โปรดจำไว้ว่าเวกเตอร์ไม่จำเป็นต้องตรวจสอบช่วงโดยใช้ [] การใช้. at () ทำสิ่งเดียวกับ [] แต่ทำการตรวจสอบระยะไกล
David Thornley

4
A vector ไม่ปรับขนาดอัตโนมัติเมื่อเข้าถึงองค์ประกอบที่ไม่อยู่ในขอบเขต! มันเป็นแค่ UB!
Pavel Minaev

คำตอบ:


364

ยินดีต้อนรับสู่เพื่อน bestest ทุก C / C ++ Programmer ของ: พฤติกรรมที่ไม่ได้กำหนด

มีจำนวนมากที่ไม่ได้ระบุโดยมาตรฐานภาษาด้วยเหตุผลหลายประการ นี่คือหนึ่งในนั้น

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

มันอาจจะยังถ้าคุณโชคร้ายจริงๆปรากฏในการทำงานได้อย่างถูกต้อง

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

สำหรับสาเหตุที่ไม่มีการตรวจสอบขอบเขตมีคำตอบสองด้าน:

  • อาเรย์นั้นเป็นของที่เหลือจากซีซีอาเรย์นั้นจะเป็นแบบดั้งเดิมที่คุณจะได้รับ เพียงลำดับขององค์ประกอบที่มีที่อยู่ต่อเนื่อง ไม่มีการตรวจสอบขอบเขตเนื่องจากเป็นเพียงการเปิดเผยหน่วยความจำดิบ การใช้กลไกการตรวจสอบขอบเขตอย่างแข็งแกร่งนั้นแทบจะเป็นไปไม่ได้เลยใน C
  • ใน C ++ การตรวจสอบขอบเขตเป็นไปได้กับประเภทคลาส แต่อาเรย์ยังคงเป็น C-compatible ที่ใช้งานได้ มันไม่ได้เป็นคลาส นอกจากนี้ C ++ ยังสร้างขึ้นในกฎอื่นซึ่งทำให้การตรวจสอบขอบเขตไม่เหมาะ หลักการแนวทาง C ++ คือ "คุณไม่จ่ายเงินสำหรับสิ่งที่คุณไม่ได้ใช้" หากรหัสของคุณถูกต้องคุณไม่จำเป็นต้องตรวจสอบและคุณไม่ควรถูกบังคับให้จ่ายค่าใช้จ่ายสำหรับการตรวจสอบขอบเขตของรันไทม์
  • ดังนั้น C ++ นำเสนอstd::vectorเทมเพลตคลาสซึ่งอนุญาตให้ทั้งสอง operator[]ถูกออกแบบมาให้มีประสิทธิภาพ มาตรฐานภาษาไม่ต้องการให้ทำการตรวจสอบขอบเขต (แม้ว่าจะไม่ได้ห้ามก็ตาม) เวกเตอร์ยังมีat()ฟังก์ชั่นสมาชิกที่รับรองว่าจะทำการตรวจสอบขอบเขต ดังนั้นใน C ++ คุณจะได้สิ่งที่ดีที่สุดทั้งสองโลกถ้าคุณใช้เวกเตอร์ คุณจะได้รับประสิทธิภาพเหมือนอาเรย์โดยไม่มีการตรวจสอบขอบเขตและคุณสามารถใช้การเข้าถึงที่ตรวจสอบได้เมื่อคุณต้องการ

5
@ Jaif: เราใช้อาร์เรย์นี้มานาน แต่ทำไมยังไม่มีการทดสอบเพื่อตรวจสอบข้อผิดพลาดง่ายๆ
seg.server.fault

7
หลักการออกแบบ C ++ คือไม่ควรช้ากว่ารหัส C ที่เทียบเท่าและ C ไม่ได้ทำการตรวจสอบอาเรย์ที่ถูกผูกไว้ โดยทั่วไปแล้วหลักการออกแบบ C นั้นใช้ความเร็วในการเขียนโปรแกรมเป็นหลัก การตรวจสอบขอบเขตอาร์เรย์ต้องใช้เวลาจึงไม่เสร็จสิ้น สำหรับการใช้งานส่วนใหญ่ใน C ++ คุณควรใช้คอนเทนเนอร์แทนอาร์เรย์อยู่แล้วและคุณสามารถเลือกตรวจสอบที่ถูกผูกไว้หรือไม่ตรวจสอบที่ถูกผูกไว้โดยเข้าถึงองค์ประกอบผ่าน. at () หรือ [] ตามลำดับ
KTC

4
@seg การตรวจสอบดังกล่าวมีค่าใช้จ่ายบางอย่าง หากคุณเขียนรหัสที่ถูกต้องคุณไม่ต้องการจ่ายราคานั้น ต้องบอกว่าฉันกลายเป็นตัวแปลงที่สมบูรณ์ไปยัง std :: vector's at () วิธีซึ่งตรวจสอบแล้ว ใช้มันมีข้อผิดพลาดค่อนข้าง exxposed ในสิ่งที่ฉันคิดว่าเป็นรหัส "ถูกต้อง"

10
ฉันเชื่อว่า GCC เวอร์ชันเก่าเปิดตัว Emacs จริงและเป็นแบบจำลองของหอคอยแห่งฮานอยในนั้นเมื่อพบพฤติกรรมบางอย่างที่ไม่ได้กำหนด อย่างที่ฉันพูดอะไรอาจเกิดขึ้น ;)
2552

4
ทุกสิ่งถูกกล่าวไปแล้วดังนั้นนี่เป็นเพียงข้อมูลแนบท้ายเล็กน้อย Debug builds สามารถให้อภัยได้มากในสถานการณ์เหล่านี้เมื่อเปรียบเทียบกับ build build เนื่องจากข้อมูลการดีบักถูกรวมอยู่ในไบนารีการดีบักจึงมีโอกาสน้อยกว่าที่บางสิ่งที่สำคัญจะถูกเขียนทับ บางครั้งนั่นเป็นสาเหตุที่ debug builds ทำงานได้ดีในขณะที่ release build crash
รวย

31

ใช้ G ++ -fstack-protector-allคุณสามารถเพิ่มตัวเลือกบรรทัดคำสั่ง:

ในตัวอย่างของคุณมันทำให้เกิดสิ่งต่อไปนี้:

> g++ -o t -fstack-protector-all t.cc
> ./t
3
4
/bin/bash: line 1: 15450 Segmentation fault      ./t

มันไม่ได้ช่วยให้คุณค้นพบหรือแก้ปัญหา แต่อย่างน้อย segfault จะทำให้คุณรู้ว่ามีบางอย่างผิดปกติ


10
ฉันเพิ่งพบตัวเลือกที่ดีกว่า: -fmudflap
Hi-Angel

1
@ Hi-Angel: เทียบเท่าที่ทันสมัย-fsanitize=addressซึ่งจับข้อบกพร่องนี้ทั้งในเวลารวบรวม (ถ้าการเพิ่มประสิทธิภาพ) และที่รันไทม์
Nate Eldredge

@NateEldredge +1 -fsanitize=undefined,addressปัจจุบันผมยังใช้ แต่มันก็เป็นที่น่าสังเกตว่ามีกรณีที่หายากมุมกับห้องสมุดมาตรฐานเมื่อออกจากการเข้าถึงขอบเขตไม่ได้ตรวจพบโดยเจลทำความสะอาด ด้วยเหตุนี้ฉันขอแนะนำให้ใช้-D_GLIBCXX_DEBUGตัวเลือกเพิ่มเติมซึ่งเพิ่มการตรวจสอบเพิ่มเติม
Hi-Angel

12

g ++ ไม่ได้ตรวจสอบขอบเขตของอาเรย์และคุณอาจจะเขียนทับสิ่งที่มี 3,4 แต่ไม่มีอะไรสำคัญถ้าคุณลองด้วยจำนวนที่สูงขึ้นคุณจะได้รับความผิดพลาด

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

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


6
คุณจะได้รับถ้าจากที่ที่อยู่ของอาร์เรย์ [3] และอาร์เรย์ [4] มี "ไม่มีอะไรสำคัญจริงๆ" ??
namezero

7

มันเป็นพฤติกรรมที่ไม่ได้กำหนดเท่าที่ฉันรู้ เรียกใช้โปรแกรมที่มีขนาดใหญ่ขึ้นด้วยสิ่งนั้นและมันจะขัดข้องที่ไหนสักแห่งระหว่างทาง การตรวจสอบขอบเขตไม่ได้เป็นส่วนหนึ่งของอาร์เรย์ดิบ (หรือแม้แต่ std :: vector)

ใช้ std :: vector ด้วยstd::vector::iterator's แทนดังนั้นคุณไม่ต้องกังวลกับมัน

แก้ไข:

เพื่อความสนุกเรียกใช้สิ่งนี้และดูว่าคุณจะเสียเวลานานเท่าใด:

int main()
{
   int array[1];

   for (int i = 0; i != 100000; i++)
   {
       array[i] = i;
   }

   return 0; //will be lucky to ever reach this
}

Edit2:

อย่าเรียกใช้สิ่งนั้น

edit3:

ตกลงนี่คือบทเรียนสั้น ๆ เกี่ยวกับอาร์เรย์และความสัมพันธ์กับพอยน์เตอร์:

เมื่อคุณใช้การทำดัชนีอาเรย์คุณจะใช้พอยน์เตอร์ปลอมตัว (เรียกว่า "การอ้างอิง") ​​ซึ่งถูกอ้างถึงโดยอัตโนมัติ นี่คือเหตุผลที่แทนที่จะเป็น * (อาร์เรย์ [1]) อาร์เรย์ [1] จะส่งคืนค่าที่ค่านั้นโดยอัตโนมัติ

เมื่อคุณมีตัวชี้ไปยังอาร์เรย์เช่นนี้

int array[5];
int *ptr = array;

จากนั้น "อาร์เรย์" ในการประกาศครั้งที่สองจะสลายตัวชี้ไปยังอาร์เรย์แรกจริงๆ นี่เป็นพฤติกรรมที่เทียบเท่ากับสิ่งนี้:

int *ptr = &array[0];

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

int main()
{
   int array[1];
   int *ptr = array;

   for (int i = 0; i != 100000; i++, ptr++)
   {
       *ptr++ = i;
   }

   return 0; //will be lucky to ever reach this
}

คอมไพเลอร์จะไม่บ่นเพราะในการเขียนโปรแกรมคุณมักจะต้องสื่อสารกับโปรแกรมอื่น ๆ โดยเฉพาะระบบปฏิบัติการ ทำกับพอยน์เตอร์ได้ค่อนข้างน้อย


3
ฉันคิดว่าคุณลืมที่จะเพิ่ม "ptr" ในตัวอย่างสุดท้ายของคุณที่นั่น คุณได้สร้างรหัสที่กำหนดชัดเจนบางอย่างโดยไม่ตั้งใจ
เจฟฟ์ทะเลสาบ

1
ฮ่าฮ่า, ดูว่าทำไมคุณไม่ควรใช้อาร์เรย์ดิบ?
jkeys

"นี่คือสาเหตุที่แทนที่จะเป็น * (array [1]), array [1] จะส่งคืนค่าที่ค่านั้นโดยอัตโนมัติ" คุณแน่ใจ * (อาร์เรย์ [1]) จะทำงานอย่างถูกต้องหรือไม่ ฉันคิดว่ามันควรเป็น * (อาร์เรย์ + 1) ps: ฮ่า ๆ มันเหมือนกับการส่งข้อความถึงอดีต แต่อย่างไรก็ตาม:
muyustan

5

เปรย

หากคุณต้องการมีอาร์เรย์ขนาด จำกัด อย่างรวดเร็วพร้อมการตรวจสอบข้อผิดพลาดช่วงลองใช้boost :: array (เช่นอาร์เรย์std :: tr1 ::จาก<tr1/array>นั้นจะเป็นคอนเทนเนอร์มาตรฐานในข้อกำหนด C ++ ถัดไป) มันเร็วมากแล้ว std :: vector มันจองหน่วยความจำในกองหรืออินสแตนซ์ของชั้นเรียนเช่นเดียวกับ int อาร์เรย์ []
นี่คือตัวอย่างรหัสง่ายๆ:

#include <iostream>
#include <boost/array.hpp>
int main()
{
    boost::array<int,2> array;
    array.at(0) = 1; // checking index is inside range
    array[1] = 2;    // no error check, as fast as int array[2];
    try
    {
       // index is inside range
       std::cout << "array.at(0) = " << array.at(0) << std::endl;

       // index is outside range, throwing exception
       std::cout << "array.at(2) = " << array.at(2) << std::endl; 

       // never comes here
       std::cout << "array.at(1) = " << array.at(1) << std::endl;  
    }
    catch(const std::out_of_range& r)
    {
        std::cout << "Something goes wrong: " << r.what() << std::endl;
    }
    return 0;
}

โปรแกรมนี้จะพิมพ์:

array.at(0) = 1
Something goes wrong: array<>: index out of range

4

C หรือ C ++ จะไม่ตรวจสอบขอบเขตของการเข้าถึงอาร์เรย์

คุณกำลังจัดสรรอาเรย์บนสแต็ก การทำดัชนีอาร์เรย์ผ่านarray[3]เท่ากับ * (array + 3)โดยที่ array คือตัวชี้ไปยัง & array [0] สิ่งนี้จะส่งผลให้พฤติกรรมที่ไม่ได้กำหนด

วิธีหนึ่งที่จะจับนี้บางครั้งใน C คือการใช้การตรวจสอบแบบคงที่เช่นเข้าเฝือก หากคุณทำงาน:

splint +bounds array.c

บน,

int main(void)
{
    int array[1];

    array[1] = 1;

    return 0;
}

จากนั้นคุณจะได้รับคำเตือน:

array.c: (ในฟังก์ชั่น main) array.c: 5: 9: มีแนวโน้มที่จะเก็บนอก: array [1] ไม่สามารถแก้ไขข้อ จำกัด : ต้องการ 0> = 1 ที่จำเป็นเพื่อตอบสนองเงื่อนไข: ต้องการ maxSet (array @ array .c: 5: 9)> = 1 การเขียนหน่วยความจำอาจเขียนไปยังที่อยู่นอกเหนือจากบัฟเฟอร์ที่จัดสรร


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

1
การกล่าวว่า "C / C ++ จะไม่ตรวจสอบขอบเขต" ไม่ถูกต้องทั้งหมด - ไม่มีสิ่งใดกีดกันการใช้งานตามมาตรฐานเฉพาะจากการทำเช่นนั้นไม่ว่าจะโดยค่าเริ่มต้นหรือด้วยการรวบรวมการตั้งค่าสถานะบางอย่าง เป็นเพียงว่าไม่มีใครสนใจ
Pavel Minaev

3

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


2
สแต็กถูกเขียนทับหรือไม่ขึ้นอยู่กับแพลตฟอร์ม
Chris Cleeland

3

รันสิ่งนี้ผ่านValgrindและคุณอาจเห็นข้อผิดพลาด

ดังที่ Falaina ชี้ให้เห็น valgrind ไม่ได้ตรวจจับการทุจริตสแต็กจำนวนมาก ฉันลองตัวอย่างภายใต้ valgrind และมันก็รายงานข้อผิดพลาดเป็นศูนย์ อย่างไรก็ตาม Valgrind สามารถเป็นเครื่องมือในการค้นหาปัญหาหน่วยความจำประเภทอื่น ๆ มากมายมันไม่ได้มีประโยชน์อย่างยิ่งในกรณีนี้เว้นแต่คุณจะแก้ไข bulid ของคุณให้มีตัวเลือก --stack-check หากคุณสร้างและเรียกใช้ตัวอย่างเป็น

g++ --stack-check -W -Wall errorRange.cpp -o errorRange
valgrind ./errorRange

valgrind จะรายงานข้อผิดพลาด


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

@Falaina - จุดดี แต่ Valgrind สามารถตรวจพบข้อผิดพลาดอย่างน้อยสแต็ก
โทมัสอ้วนใหญ่

และ valgrind จะไม่เห็นความผิดพลาดใด ๆ กับโค้ดเพราะคอมไพเลอร์ฉลาดพอที่จะปรับอาเรย์ให้เหมาะสมและเพียงแค่เอาตัวอักษร 3 และ 4 ออกมาการเพิ่มประสิทธิภาพนั้นเกิดขึ้นก่อนที่ gcc จะตรวจสอบขอบเขตของอาเรย์ มีไม่แสดง
Goswin von Brederlow

2

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


5
ไม่พฤติกรรมที่ไม่ได้กำหนด "ทำงานในความโปรดปรานของคุณ" เมื่อเกิดปัญหาอย่างสมบูรณ์ เมื่อมันดูเหมือนว่าทำงานได้นั่นเป็นสถานการณ์ที่เลวร้ายที่สุด
jalf

@JohnBode: ถ้าเป็นเช่นนั้นจะดีกว่าถ้าคุณแก้ไขข้อความตามความคิดเห็นของ jalf
Destructor

1

เมื่อคุณเริ่มต้นอาร์เรย์ด้วยint array[2]จะมีการจัดสรรพื้นที่สำหรับจำนวนเต็ม 2 จำนวน แต่ตัวระบุarrayเพียงชี้ไปที่จุดเริ่มต้นของพื้นที่นั้น เมื่อคุณเข้าถึงarray[3]และarray[4]คอมไพเลอร์จากนั้นก็เพิ่มขึ้นอยู่ที่จะชี้ไปที่ค่าเหล่านั้นจะเป็นถ้าอาร์เรย์เป็นเวลานานพอ; ลองเข้าถึงบางสิ่งเช่นarray[42]โดยไม่เริ่มต้นก่อนคุณจะได้รับสิ่งที่มีค่าที่อยู่ในหน่วยความจำแล้ว

แก้ไข:

ข้อมูลเพิ่มเติมเกี่ยวกับพอยน์เตอร์ / อาร์เรย์: http://home.netcom.com/~tjensen/ptr/pointers.htm


0

เมื่อคุณประกาศ int array [2]; คุณจองพื้นที่หน่วยความจำ 2 แห่งแต่ละ 4 ไบต์ (โปรแกรม 32 บิต) หากคุณพิมพ์ array [4] ในรหัสของคุณมันยังคงสอดคล้องกับการโทรที่ถูกต้อง แต่ในเวลาทำงานเท่านั้นมันจะโยนข้อยกเว้นที่ไม่สามารถจัดการได้ C ++ ใช้การจัดการหน่วยความจำด้วยตนเอง นี่เป็นข้อบกพร่องด้านความปลอดภัยที่ใช้สำหรับการแฮ็กโปรแกรม

สิ่งนี้สามารถช่วยให้เข้าใจ:

int * somepointer;

somepointer [0] = somepointer [5];


0

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


0

เมื่อคุณเขียน 'array [index]' ใน C มันจะแปลเป็นคำสั่งของเครื่อง

การแปลเป็นไปอย่างเช่น:

  1. 'รับที่อยู่ของอาร์เรย์'
  2. 'รับขนาดของประเภทของวัตถุอาร์เรย์ประกอบด้วย'
  3. 'คูณขนาดของประเภทด้วยดัชนี'
  4. 'เพิ่มผลลัพธ์ไปยังที่อยู่ของอาร์เรย์'
  5. 'อ่านสิ่งที่อยู่ที่เกิดขึ้น'

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


0

วิธีการที่ดีที่ฉันได้เห็นบ่อยครั้งและฉันถูกนำมาใช้จริง ๆ แล้วก็คือการฉีดองค์ประกอบประเภท NULL บางอย่าง (หรือสิ่งที่สร้างขึ้นเช่นuint THIS_IS_INFINITY = 82862863263;) ที่ส่วนท้ายของอาร์เรย์

จากนั้นที่การตรวจสอบสภาพลูปTYPE *pagesWordsคืออาเรย์ตัวชี้บางชนิด:

int pagesWordsLength = sizeof(pagesWords) / sizeof(pagesWords[0]);

realloc (pagesWords, sizeof(pagesWords[0]) * (pagesWordsLength + 1);

pagesWords[pagesWordsLength] = MY_NULL;

for (uint i = 0; i < 1000; i++)
{
  if (pagesWords[i] == MY_NULL)
  {
    break;
  }
}

วิธีแก้ปัญหานี้จะไม่เกิดอะไรขึ้นถ้าอาร์เรย์เต็มไปด้วยstructประเภท


0

ดังที่ได้กล่าวไปแล้วในคำถามโดยใช้ std :: vector :: at จะแก้ปัญหาและทำการตรวจสอบที่ถูกผูกไว้ก่อนที่จะเข้าถึง

หากคุณต้องการอาร์เรย์ขนาดคงที่ที่ตั้งอยู่บนสแต็กเป็นรหัสแรกของคุณให้ใช้คอนเทนเนอร์ C ++ 11 ใหม่ std :: array; เป็นเวกเตอร์ที่มี std :: array :: at function ในความเป็นจริงฟังก์ชั่นที่มีอยู่ในภาชนะมาตรฐานทั้งหมดที่มันมีความหมายคือที่กำหนดตัวดำเนินการ [] :( deque, map, unordered_map) ด้วยข้อยกเว้นของ std :: bitset ที่มันเรียกว่า std :: bitset: :ทดสอบ.


0

libstdc ++ ซึ่งเป็นส่วนหนึ่งของ gcc มีโหมดดีบักพิเศษสำหรับการตรวจสอบข้อผิดพลาด -D_GLIBCXX_DEBUGมันถูกเปิดใช้งานโดยธงคอมไพเลอร์ เหนือสิ่งอื่นใดมันตรวจสอบขอบเขตอย่างstd::vectorมีประสิทธิภาพ นี่คือตัวอย่างออนไลน์พร้อม gcc รุ่นล่าสุด

ดังนั้นจริง ๆ แล้วคุณสามารถทำการตรวจสอบขอบเขตด้วยโหมดดีบัก libstdc ++ แต่คุณควรทำเฉพาะเมื่อการทดสอบเพราะมันมีค่าใช้จ่ายประสิทธิภาพที่โดดเด่นเมื่อเทียบกับโหมด libstdc ++ ปกติ


0

หากคุณเปลี่ยนโปรแกรมเล็กน้อย:

#include <iostream>
using namespace std;
int main()
{
    int array[2];
    INT NOTHING;
    CHAR FOO[4];
    STRCPY(FOO, "BAR");
    array[0] = 1;
    array[1] = 2;
    array[3] = 3;
    array[4] = 4;
    cout << array[3] << endl;
    cout << array[4] << endl;
    COUT << FOO << ENDL;
    return 0;
}

(การเปลี่ยนแปลงตัวพิมพ์ใหญ่ - ใส่ตัวพิมพ์เล็กถ้าคุณจะลองทำ)

คุณจะเห็นว่าตัวแปรfooถูกทิ้งในถังขยะ รหัสของคุณจะเก็บค่าลงในอาร์เรย์ที่ไม่มีอยู่ [3] และอาเรย์ [4], และสามารถที่จะถูกดึงพวกเขา แต่การจัดเก็บข้อมูลที่เกิดขึ้นจริงที่ใช้จะมาจากfoo

ดังนั้นคุณจึงสามารถ "รับห่าง" กับเกินขอบเขตของอาร์เรย์ในตัวอย่างเดิมของคุณ แต่ค่าใช้จ่ายในการก่อให้เกิดความเสียหายที่อื่น ๆ - ความเสียหายที่อาจพิสูจน์ให้เป็นมากยากที่จะวินิจฉัย

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

C ++ ใช้ C ซึ่งออกแบบมาให้ใกล้เคียงกับภาษาแอสเซมบลีมากที่สุด

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