พลังการคำนวณสูงสุดของการนำ C ไปใช้งาน


28

ถ้าเราไปตามหนังสือ (หรือรุ่นอื่น ๆ ของข้อกำหนดภาษาถ้าคุณต้องการ), การใช้พลังงาน C สามารถมีเท่าไหร่?

โปรดทราบว่า“ การติดตั้ง C” มีความหมายทางเทคนิค: มันเป็นอินสแตนซ์เฉพาะของสเปคภาษาการเขียนโปรแกรม C ที่มีการบันทึกพฤติกรรมการใช้งานที่กำหนดไว้ การติดตั้ง AC ไม่จำเป็นต้องสามารถทำงานบนคอมพิวเตอร์จริงได้ มันต้องใช้ภาษาทั้งหมดรวมถึงวัตถุทุกชิ้นที่มีการแทนค่าบิตสตริงและประเภทที่มีขนาดที่กำหนดการนำไปใช้

สำหรับวัตถุประสงค์ของคำถามนี้ไม่มีที่เก็บข้อมูลภายนอก อินพุต / เอาต์พุตเดียวที่คุณสามารถทำได้คือgetchar(เพื่ออ่านอินพุตโปรแกรม) และputchar(เพื่อเขียนเอาต์พุตของโปรแกรม) นอกจากนี้ยังมีโปรแกรมใด ๆ ที่จะเรียกไม่ได้กำหนดพฤติกรรมที่ไม่ถูกต้อง: โปรแกรมที่ถูกต้องจะต้องมีพฤติกรรมที่กำหนดโดยสเปค C บวกรายละเอียดการดำเนินงานของพฤติกรรมการดำเนินงานที่กำหนดไว้ในภาคผนวก J (สำหรับ C99) โปรดทราบว่าการเรียกฟังก์ชั่นห้องสมุดที่ไม่ได้กล่าวถึงในมาตรฐานเป็นพฤติกรรมที่ไม่ได้กำหนด

ปฏิกิริยาเริ่มต้นของฉันคือการใช้งาน C ไม่มีอะไรมากไปกว่าระบบ จำกัด อัตโนมัติเนื่องจากมันมีข้อ จำกัด เกี่ยวกับจำนวนหน่วยความจำที่กำหนดแอดเดรสได้ (คุณไม่สามารถจัดการกับหน่วยความจำได้มากกว่าsizeof(char*) * CHAR_BITบิตเนื่องจากหน่วยความจำที่แตกต่างกัน ในตัวชี้ไบต์)

อย่างไรก็ตามฉันคิดว่าการใช้งานสามารถทำได้มากกว่านี้ เท่าที่ฉันสามารถบอกได้มาตรฐานไม่มีข้อ จำกัด เกี่ยวกับความลึกของการเรียกซ้ำ ดังนั้นคุณสามารถเรียกใช้ฟังก์ชันเรียกซ้ำได้มากเท่าที่คุณต้องการเฉพาะการโทรจำนวน จำกัด เท่านั้นที่ต้องใช้registerอาร์กิวเมนต์ที่ไม่สามารถระบุตำแหน่งได้ ( ) ดังนั้นการดำเนินงาน C ที่ช่วยให้การเรียกซ้ำโดยพลการและมีการ จำกัด จำนวนของregisterวัตถุที่สามารถเข้ารหัสกำหนดขยายลงออโต

ถูกต้องหรือไม่ คุณจะพบว่าการติดตั้ง C ที่มีประสิทธิภาพยิ่งขึ้น? มีการนำ C ทัวริงไปใช้หรือไม่?


4
@Dave: ตามที่ Gilles อธิบายไว้ดูเหมือนว่าคุณสามารถมีหน่วยความจำที่ไม่มีขอบเขตได้ แต่ไม่มีวิธีจัดการกับมันโดยตรง
Jukka Suomela

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

3
สิ่งหนึ่งที่ต้องจำไว้คือมีหลายวิธีในการกระตุ้นให้เกิด "พฤติกรรมที่กำหนดโดยการนำไปปฏิบัติ" (หรือ "พฤติกรรมที่ไม่ได้กำหนด") และโดยทั่วไปการใช้งานสามารถให้เช่นฟังก์ชั่นห้องสมุดที่ให้ฟังก์ชั่นที่ไม่ได้กำหนดไว้ในมาตรฐาน C ทั้งหมดนี้ให้ "ช่องโหว่" ที่คุณสามารถเข้าถึงได้พูดเป็นเครื่องจักรทัวริงที่สมบูรณ์ หรือแม้กระทั่งบางสิ่งที่แข็งแกร่งกว่าเช่นพยากรณ์ที่แก้ปัญหาการหยุด ตัวอย่างที่โง่เขลา: พฤติกรรมที่กำหนดโดยการนำไปปฏิบัติของการโอเวอร์โฟลว์จำนวนเต็มที่ลงนามหรือการแปลงจำนวนเต็ม - ตัวชี้จะช่วยให้คุณเข้าถึง oracle ได้
Jukka Suomela

7
อย่างไรก็ตามอาจเป็นความคิดที่ดีที่จะเพิ่มแท็ก "สันทนาการ" (หรืออะไรก็ตามที่เราใช้ในการไขปริศนา) เพื่อให้ผู้คนไม่ใส่ใจกับเรื่องนี้มากเกินไป เห็นได้ชัดว่าเป็น "คำถามที่ผิด" ที่จะถาม แต่อย่างไรก็ตามฉันพบว่ามันน่าขบขันและน่าสนใจ :)
Jukka Suomela

2
@ Jukka: ความคิดที่ดี ตัวอย่างเช่นโอเวอร์โฟลว์โดย X = write X / 3 บนเทปและเคลื่อนที่ในทิศทาง X% 3, อันเดอร์โฟลว์ = กระตุ้นสัญญาณที่สอดคล้องกับสัญลักษณ์บนเทป มันให้ความรู้สึกเหมือนเป็นการล่วงละเมิด แต่มันอยู่ในใจของคำถามของฉัน คุณเขียนมันเป็นคำตอบได้ไหม? (@others: ไม่ใช่ว่าฉันต้องการกีดกันคำแนะนำที่ฉลาดเช่นนี้!)
Gilles 'เลิกเป็นคนชั่ว'

คำตอบ:


8

ดังที่ระบุไว้ในคำถามมาตรฐาน C กำหนดให้มีค่า UCHAR_MAX อยู่เช่นนั้นตัวแปรทุกประเภทunsigned charจะเก็บค่าระหว่าง 0 ถึง UCHAR_MAX เสมอ มันยังต้องใช้ว่าทุกวัตถุที่จัดสรรแบบไดนามิกจะแสดงโดยลำดับของไบต์ที่สามารถระบุตัวตนผ่านตัวชี้ของประเภทunsigned char*และที่มีอย่างต่อเนื่องsizeof(unsigned char*)ดังกล่าวว่าตัวชี้ชนิดว่าทุกที่จะระบุตัวตนได้โดยลำดับของค่าประเภทsizeof(unsigned char *) unsigned charจำนวนของวัตถุที่สามารถจัดสรรพร้อมกันแบบไดนามิกจึงถูก จำกัด ไว้ที่ ) ไม่มีสิ่งใดที่จะป้องกันนักแปลเชิงทฤษฎีจากการกำหนดค่าของค่าคงที่เหล่านั้นเพื่อสนับสนุนวัตถุมากกว่า 10 10 10แต่จากมุมมองทางทฤษฎีการดำรงอยู่ของขอบเขตใด ๆ ไม่ว่าขนาดใหญ่หมายความว่าสิ่งที่ไม่สิ้นสุดUCHAR_MAXsizeof(unsigned char)101010

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

อย่างไรก็ตามมีรอยย่นที่อาจเกิดขึ้นได้อีก: มันเป็นสิ่งจำเป็นว่าหากโปรแกรมตรวจสอบลำดับของอักขระที่มีความยาวคงที่ซึ่งเชื่อมโยงกับตัวชี้สองตัวไปยังวัตถุที่แตกต่างกันลำดับเหล่านั้นจะต้องไม่ซ้ำกัน เพราะมีเพียงUCHAR_MAXsizeof(unsigned char)ลำดับที่เป็นไปได้ของค่าตัวโปรแกรมที่สร้างจำนวนตัวชี้ไปยังวัตถุที่แตกต่างกันในส่วนที่เกินจากที่ไม่สามารถปฏิบัติตามมาตรฐานซีหากรหัสที่เคยตรวจสอบลำดับของตัวละครที่เกี่ยวข้องกับคำแนะนำเหล่านั้น มันอาจเป็นไปได้ในบางกรณีอย่างไรก็ตามสำหรับคอมไพเลอร์เพื่อตรวจสอบว่าไม่มีรหัสใดที่จะตรวจสอบลำดับของอักขระที่เกี่ยวข้องกับตัวชี้ ถ้าแต่ละคน "ถ่าน" ที่จริงก็คือความสามารถในการถือครองจำนวนเต็ม จำกัด ใด ๆ และหน่วยความจำของเครื่องเป็นลำดับนับอนันต์ของจำนวนเต็ม [รับเครื่องทัวริงไม่ จำกัด เทปใครสามารถเลียนแบบเครื่องดังกล่าวถึงแม้ว่ามันจะเป็นจริงๆช้า] จากนั้น มันจะเป็นไปได้ที่จะทำให้ C เป็นภาษาทัวริงที่สมบูรณ์


ด้วยเครื่องจักรเช่นนี้ขนาดของอะไร (ถ่าน) จะกลับมา?
TLW

1
@TLW: เหมือนกับเครื่องอื่น ๆ : 1. มาโคร CHAR_BITS และ CHAR_MAX นั้นจะมีปัญหามากกว่านี้เล็กน้อย; มาตรฐานจะไม่อนุญาตให้มีแนวคิดประเภทที่ไม่มีขอบเขต
supercat

อ๊ะฉันหมายถึง CHAR_BITS อย่างที่คุณพูดขอโทษ
TLW

7

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

การสร้างเธรดใหม่ทำให้ได้สแต็กที่สอง สองกองก็เพียงพอสำหรับทัวริงครบถ้วน กองหนึ่งหมายถึงสิ่งที่อยู่ทางด้านซ้ายของหัวกองอื่น ๆ ที่อยู่ทางขวา


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

3

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

  • กำหนดโครงสร้างที่สามารถใช้เป็นรายการเชื่อมโยงคู่สำหรับการแสดงเทป
    typdef struct {
      cell_t * pred; // เซลล์ทางด้านซ้าย
      cell_t * succ; // เซลล์ทางด้านขวา
      int val; // ค่าของเซลล์
    } cell_t 

headจะชี้ไปที่cell_tโครงสร้าง

  • กำหนดโครงสร้างที่สามารถใช้ในการจัดเก็บสถานะปัจจุบันและธง
    typedef struct {
      รัฐ int;
      ธง int;
    } info_t 
  • จากนั้นกำหนดฟังก์ชันลูปเดี่ยวที่จำลอง Universal TM เมื่อส่วนหัวอยู่ระหว่างขอบเขตของรายการที่ลิงก์สองชั้น เมื่อหัวชนขอบตั้งธงของโครงสร้าง info_t (HIT_LEFT, HIT_RIGHT) และกลับมา:
ถือเป็นโมฆะ simulate_UTM (cell_t * head, info_t * info) {
  ในขณะที่ (จริง) {
    head-> val = UTM_nextsymbol [info-> state, head-> val]; // เขียนสัญลักษณ์
    info-> state = UTM_nextstate [info-> state, head-> val]; // สถานะถัดไป
    if (info-> state == HALT_STATE) {// พิมพ์ถ้ายอมรับและออกจากโปรแกรม
       putchar ((info-> state == ACCEPT_STATE)? '1': '0');
       ออก (0);
    }
    int move = UTM_nextmove [info-> state, head-> val];
    ถ้า (move == MOVE_LEFT) {
      หัว = หัว -> pred; // เลื่อนไปทางซ้าย
      if (head == NULL) {info-> flag = HIT_LEFT; กลับ; }
    } อื่น {
      หัว = หัว -> succ; // เลื่อนไปทางขวา
      if (head == NULL) {info-> flag = HIT_RIGHT; กลับ; }
    }
  } // ยังอยู่ในขอบเขต ... ดำเนินต่อไป
}
  • จากนั้นกำหนดฟังก์ชั่นแบบเรียกซ้ำที่เรียกรูทีน UTM จำลองก่อนแล้วจึงเรียกตัวเองซ้ำเมื่อต้องการขยายเทป เมื่อต้องการขยายเทปด้านบน (HIT_RIGHT) ไม่มีปัญหาเมื่อต้องการเลื่อนด้านล่าง (HIT_LEFT) เพียงเลื่อนค่าของเซลล์โดยใช้รายการที่เชื่อมโยงสองครั้ง:
ตัว void stacker (cell_t * top, cell_t * bottom, cell_t * head, info_t * ข้อมูล) {
  simulate_UTM (หัว, ข้อมูล);
  cell_t newcell; // เซลล์ใหม่
  newcell.pred = top; // อัปเดตรายการที่ลิงก์สองครั้งด้วยเซลล์ใหม่
  newcell.succ = NULL;
  top-> succ = & newcell;
  newcell.val = EMPTY_SYMBOL;

  สวิตช์ (ข้อมูล -> จำนวนครั้ง) {
    กรณี HIT_RIGHT:
      stacker (& newcell, ด้านล่าง, newcell, ข้อมูล);
      ทำลาย;
    กรณี HIT_BOTTOM:
      cell_t * tmp = newcell;
      ในขณะที่ (tmp-> pred! = NULL) {// เลื่อนค่า
        tmp-> val = tmp-> pred-> val;
        tmp = tmp-> pred;
      }
      tmp-> val = EMPTY_SYMBOL;
      รถยก (& newcell, ด้านล่าง, ด้านล่าง, ข้อมูล);
      ทำลาย;
  }
}
  • เทปเริ่มต้นสามารถเติมเต็มด้วยฟังก์ชั่นวนซ้ำอย่างง่ายที่สร้างรายการเชื่อมโยงสองชั้นแล้วเรียกใช้stackerฟังก์ชันเมื่ออ่านสัญลักษณ์สุดท้ายของเทปป้อนข้อมูล (โดยใช้ readchar)
โมฆะ init_tape (cell_t * ด้านบน, cell_t * ด้านล่าง, info_t * ข้อมูล) {
  cell_t newcell;
  int c = readchar ();
  ถ้า (c == END_OF_INPUT) รถยก (& บน, ล่าง, ล่าง, ข้อมูล); // ไม่มีสัญลักษณ์เพิ่มเติมให้เริ่ม
  newcell.pred = top;
  if (top! = NULL) top.succ = & newcell; else bottom = & newcell;
  init_tape (& newcell, ด้านล่าง, ข้อมูล);
}

แก้ไข:หลังจากคิดเล็กน้อยเกี่ยวกับเรื่องนี้มีปัญหากับพอยน์เตอร์ ...

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


3
ทุกครั้งที่ถูกเรียกว่ามันจะสร้างวัตถุใหม่stacker newcellวัตถุนี้มีที่อยู่ซึ่งแตกต่างจากที่อยู่ของวัตถุอื่น ๆ ดังนั้นจึงมีจำนวน จำกัด สูงสุดต่อความลึกของการเรียกซ้ำstacker(น้อยกว่าเล็กน้อยโดยที่nคือจำนวนบิตต่อตัวชี้และs = ) เช่นเดียวกับโปรแกรมที่จะเรียกใช้พฤติกรรมที่ไม่ได้กำหนดเมื่อมันหมด“ กอง” ถ้าคุณจำกัดความลึกของการเรียกซ้ำคุณจะได้รับออโตเมติก จำกัด ใช่ไหม? 2n/sns=sizeof(cell_t)
Gilles 'หยุดความชั่วร้าย'

@Gilles: ถูกต้อง (ดูการแก้ไขของฉัน); ถ้าคุณจำกัดความลึกของการเรียกซ้ำคุณจะได้รับหุ่นยนต์ที่ จำกัด
Marzio De Biasi

@MarzioDeBiasi ไม่เขาผิดเพราะเขาอ้างถึงการใช้งานที่เป็นรูปธรรมซึ่งมาตรฐานไม่ได้กำหนดไว้ล่วงหน้า ในความเป็นจริงไม่มีขีด จำกัด ทางทฤษฎีที่ความลึก recursion ใน C ตัวเลือกที่จะใช้การใช้งานแบบกองซ้อนแบบ จำกัด ไม่ได้พูดอะไรเกี่ยวกับข้อ จำกัด ทางทฤษฎีของภาษา แต่ทัวริงสมบูรณ์เป็นข้อ จำกัด ทางทฤษฎี
xamid

0

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

แก้ไข : หากคุณสามารถใช้ ram ซึ่งมี จำกัด การก่อสร้างนี้จะไม่ทำงานอีกต่อไปดังนั้นโปรดดูด้านล่าง

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

ฉันยังคาดการณ์ว่าจำนวนภาษาที่คุณจำได้นั้นมี จำกัด (แม้ว่าภาษาของตัวเองจะไม่มีที่สิ้นสุดเช่นa*ก็โอเค แต่b^kใช้ได้กับจำนวน จำกัดkเท่านั้น)

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

คุณน่าจะได้รับภาษา Type-2 ทั้งหมดด้วยเหตุผลเดียวกัน แต่ฉันไม่แน่ใจว่าคุณสามารถจัดการทั้งสองสถานะและสแต็นค่าคงที่ใน call-stack ได้หรือไม่ แต่ในบันทึกทั่วไปคุณสามารถลืม ram ได้อย่างมีประสิทธิภาพเนื่องจากคุณสามารถปรับขนาดของหุ่นยนต์ได้ตลอดเวลาเพื่อให้ตัวอักษรของคุณมีขนาดเกินความจุของแรม ดังนั้นถ้าคุณสามารถจำลอง TM ด้วยสแต็คเท่านั้น Type-2 จะเท่ากับ Type-0 ใช่ไหม?


5
“ stack-pointer” คืออะไร? (โปรดทราบว่าคำว่า "สแต็ค" ไม่ปรากฏในมาตรฐาน C) คำถามของฉันเกี่ยวกับ C เป็นภาษาทางการซึ่งไม่เกี่ยวกับการใช้งาน C บนคอมพิวเตอร์ หากคุณต้องการเข้าถึง call stack คุณต้องดำเนินการในภาษาที่จัดเตรียมโดยภาษา ตัวอย่างเช่นโดยการใช้ที่อยู่ของฟังก์ชั่นการขัดแย้ง - แต่การดำเนินการใด ๆ ที่กำหนดมีเพียงจำนวน จำกัด ของที่อยู่ซึ่งจะจำกัดความลึกของการเรียกซ้ำ
Gilles 'หยุดชั่วร้าย'

ฉันได้แก้ไขคำตอบของฉันเพื่อไม่รวมการใช้ตัวชี้สแต็ก
bitmask

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

0

ฉันคิดถึงเรื่องนี้เพียงครั้งเดียวและตัดสินใจลองใช้ภาษาที่ไม่มีบริบทโดยใช้ความหมายที่คาดหวัง ส่วนสำคัญของการนำไปใช้นั้นมีหน้าที่ดังต่อไปนี้

void *it;

void read_triple(void *back)
{
  if(read_a()) read_triple(&back);
  else reject();
  for(it = back; it != NULL; it = *it)
     if(!read_b()) reject();
  if(read_c()) return;
  else reject();
}

{anbncn}

อย่างน้อยฉันคิดว่ามันใช้งานได้ อาจเป็นได้ว่าฉันกำลังทำผิดขั้นพื้นฐานบางอย่าง

รุ่นคงที่:

void *it;

void read_triple(void *back)
{
  if(read_a()) read_triple(&back);
  else for(it = back; it != NULL; it = * (void **) it)
     if(!read_b()) reject();
  if(read_c()) return;
  else reject();
}

ก็ไม่เป็นความผิดพลาดพื้นฐาน แต่it = *itควรถูกแทนที่ด้วยit = * (void **) itเป็นอย่างอื่นเป็นประเภท*it void
Ben Standeven

ฉันจะประหลาดใจมากถ้าเดินทาง call stack เช่นนั้นจะถูกกำหนดพฤติกรรมใน C.
Radu GRIGore

โอ้นี่ใช้งานไม่ได้เพราะ 'b' ตัวแรกทำให้ read_a () ล้มเหลวและทำให้เกิดการปฏิเสธ
Ben Standeven

แต่มันถูกต้องตามกฎหมายในการเดินทาง call stack ในแบบนี้เนื่องจากมาตรฐาน C บอกว่า: "สำหรับวัตถุ [คืออันที่มีการจัดเก็บอัตโนมัติ] ที่ไม่มีประเภทอาเรย์ความยาวแปรผันอายุการใช้งานของมันขยายจากการเข้าสู่บล็อกด้วย ซึ่งมันจะสัมพันธ์กันจนกว่าการประมวลผลของบล็อกนั้นจะจบลงด้วยวิธีใด ๆ (การเข้าสู่บล็อกที่ถูกปิดล้อมหรือการเรียกใช้ฟังก์ชั่นหยุดทำงานชั่วคราว แต่ไม่สิ้นสุดการดำเนินการของบล็อกปัจจุบัน) หากบล็อกนั้นถูกเรียกซ้ำ ถูกสร้างขึ้นในแต่ละครั้ง " ดังนั้นการเรียกใช้ read_triple แต่ละครั้งจะสร้างตัวชี้ใหม่ที่สามารถใช้ในการเรียกซ้ำ
Ben Standeven

2
2CHAR_BITsizeof(char*)

0

ตามคำตอบของ @ supercat:

การเรียกร้องของความไม่สมบูรณ์ของ C ดูเหมือนว่าจะอยู่กึ่งกลางรอบวัตถุที่แตกต่างควรมีที่อยู่ที่แตกต่างกันและชุดของที่อยู่จะถือว่า จำกัด ตามที่ @supercat เขียน

ดังที่ระบุไว้ในคำถามมาตรฐาน C กำหนดให้มีค่าUCHAR_MAXดังกล่าวซึ่งตัวแปรประเภทถ่านที่ไม่ได้ลงชื่อทุกตัวจะเก็บค่าระหว่าง 0 UCHAR_MAXถึงรวม มันต้องการเพิ่มเติมว่าวัตถุที่จัดสรรแบบไดนามิกทุกตัวจะถูกแทนด้วยลำดับของไบต์ซึ่งสามารถระบุได้ผ่านตัวชี้ประเภทถ่านที่ไม่ได้ลงนาม * และมีค่าคงที่sizeof(unsigned char*)เช่นนั้นตัวชี้ทุกประเภทนั้นจะสามารถระบุได้ด้วยลำดับของsizeof(unsigned char *)ค่าชนิดที่ไม่ได้ลงนาม ถ่าน

unsigned char*N{0,1}sizeof(unsigned char*){0,1}sizeof(unsigned char)Nsizeof(unsigned char*)Nω

ณ จุดนี้เราควรตรวจสอบว่ามาตรฐาน C จะอนุญาตอย่างนั้นหรือไม่

sizeofZ


1
การดำเนินการหลายอย่างในประเภทอินทิกรัลถูกกำหนดเพื่อให้ได้ผลลัพธ์ที่เป็น“ โมดูโลที่ถูกลดลงมากกว่าค่าสูงสุดที่สามารถแสดงได้ในประเภทผลลัพธ์” มันจะทำงานอย่างไรถ้าค่าสูงสุดนั้นเป็นค่าที่ไม่ จำกัด ?
Gilles 'SO- หยุดความชั่วร้าย'

@Gilles นี่เป็นจุดที่น่าสนใจ ไม่แน่ชัดว่าอะไรคือความหมายของuintptr_t p = (uintptr_t)sizeof(void*)(วาง \ omega ในสิ่งที่มีจำนวนเต็มไม่ได้ลงนาม) ฉันไม่รู้. เราอาจหลีกเลี่ยงการกำหนดผลลัพธ์เป็น 0 (หรือหมายเลขอื่น ๆ )
Alexey B.

1
uintptr_tจะต้องไม่มีที่สิ้นสุดเช่นกัน โปรดทราบว่าประเภทนี้เป็นตัวเลือก - แต่ถ้าคุณมีจำนวนพอยน์เตอร์ที่แตกต่างกันจำนวนอนันต์sizeof(void*)ต้องเป็นอนันต์ดังนั้นsize_tจะต้องไม่ จำกัด การคัดค้านของฉันเกี่ยวกับการลดแบบโมดูโลนั้นไม่ชัดเจนนัก - มันจะเข้ามาเฉพาะในกรณีที่มีการล้น แต่ถ้าคุณอนุญาตประเภทที่ไม่มีที่สิ้นสุดพวกเขาอาจจะไม่ล้น แต่ในมือนั้นแต่ละประเภทมีค่าต่ำสุดและสูงสุดซึ่งเท่าที่ฉันสามารถบอกได้ว่ามันUINT_MAX+1ต้องล้น
Gilles 'ดังนั้นหยุดความชั่วร้าย'

ยังเป็นจุดที่ดี แน่นอนเราได้รับประเภท (พอยน์เตอร์และ size_t) ที่ควรเป็นℕ, ℤหรือสิ่งก่อสร้างบางอย่างตามพวกเขา (สำหรับ size_t หากเป็นสิ่งที่ต้องการℕ∪ {ω}) ทีนี้ถ้าสำหรับบางประเภทมาตรฐานจะต้องมีแมโครที่กำหนดค่าสูงสุด (PTR_MAX หรืออะไรทำนองนั้น) สิ่งต่าง ๆ จะมีขนดก แต่จนถึงตอนนี้ฉันสามารถรองรับความต้องการของ MIN / MAX macros สำหรับประเภทที่ไม่ใช่ตัวชี้เท่านั้น
Alexey B.

ความเป็นไปได้อีกอย่างในการตรวจสอบคือการกำหนดทั้งsize_tประเภทและประเภทของตัวชี้ให้เป็นℕ∪ {ω} นี่เป็นการกำจัดปัญหาต่ำสุด / สูงสุด ปัญหาเกี่ยวกับความหมายที่ล้นยังคงอยู่ สิ่งที่ควรเป็นความหมายของuint x = (uint)ωไม่ชัดเจนสำหรับฉัน อีกครั้งเราอาจใช้ 0 อย่างส่งเดช แต่มันดูน่าเกลียดไปหน่อย
Alexey B.
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.