ตัวชี้ C เพื่อประกาศอาร์เรย์ที่มีค่าบิตและผู้ประกอบการ


9

ฉันต้องการที่จะเข้าใจรหัสต่อไปนี้:

//...
#define _C 0x20
extern const char *_ctype_;
//...
__only_inline int iscntrl(int _c)
{
    return (_c == -1 ? 0 : ((_ctype_ + 1)[(unsigned char)_c] & _C));
}

มันมาจากไฟล์ctype.hจากรหัสที่มาระบบปฏิบัติการ obenbsd ฟังก์ชั่นนี้จะตรวจสอบว่าถ่านเป็นตัวควบคุมหรือตัวอักษรที่พิมพ์ได้ภายในช่วง ascii นี่คือห่วงโซ่ความคิดปัจจุบันของฉัน:

  1. iscntrl ('a') ถูกเรียกและ 'a' ถูกแปลงเป็นค่าจำนวนเต็ม
  2. ก่อนอื่นตรวจสอบว่า _c เป็น -1 แล้วส่งกลับ 0 อื่น ๆ
  3. เพิ่มที่อยู่ของพอยน์เตอร์ที่ไม่ได้กำหนดชี้ไปที่ 1
  4. ประกาศที่อยู่นี้เป็นตัวชี้ไปยังอาร์เรย์ของความยาว (ถ่านที่ไม่ได้ลงชื่อ) ((int) 'a')
  5. ใช้ bitwise และโอเปอเรเตอร์กับ _C (0x20) และ array (???)

ยังไงก็เถอะมันใช้งานได้และทุกครั้งที่ 0 ถูกส่งคืน char _c ที่กำหนดไม่ใช่อักขระที่พิมพ์ได้ มิฉะนั้นเมื่อพิมพ์ออกมาฟังก์ชันก็แค่คืนค่าจำนวนเต็มที่ไม่สนใจเป็นพิเศษ ปัญหาความเข้าใจของฉันอยู่ในขั้นตอนที่ 3, 4 (บิต) และ 5

ขอบคุณสำหรับความช่วยเหลือ


1
_ctype_เป็นอาร์เรย์ของ bitmasks มันถูกจัดทำดัชนีโดยตัวละครที่น่าสนใจ ดังนั้น_ctype_['A']จะมีบิตที่สอดคล้องกับ "อัลฟา" และ "ตัวพิมพ์ใหญ่" _ctype_['a']จะมีบิตที่สอดคล้องกับ "อัลฟา" และ "ตัวพิมพ์เล็ก" _ctype_['1']จะมีบิตที่สอดคล้องกับ "หลัก" ฯลฯ ดูเหมือนว่า0x20เป็นบิตที่สอดคล้องกับ "ควบคุม" . แต่ด้วยเหตุผลบางอย่าง_ctype_อาเรย์จะถูกชดเชยด้วย 1 ดังนั้นบิตของ'a'มันจึงเข้า_ctype_['a'+1]มา (นั่นอาจจะทำให้มันทำงานEOFได้โดยไม่ต้องมีการทดสอบเพิ่มเติม)
Steve Summit

นักแสดง(unsigned char)คือการดูแลความเป็นไปได้ที่ตัวละครจะถูกเซ็นชื่อและติดลบ
Steve Summit

คำตอบ:


3

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

มาตรฐาน C กำหนดสิ่งนี้สำหรับฟังก์ชัน ctype.h ทั้งหมด:

ในทุกกรณีอาร์กิวเมนต์เป็นintค่าที่จะสามารถแทนได้ในฐานะunsigned charหรือจะเท่ากับค่าของแมโครEOF

จะผ่านรหัสทีละขั้นตอน:

  • int iscntrl(int _c)intประเภทจริงๆตัวอักษร แต่ฟังก์ชั่น ctype.h ทุกคนจะต้องจับดังนั้นพวกเขาจะต้องEOFint
  • กับการตรวจสอบ-1คือการตรวจสอบกับเพราะมันมีค่าEOF-1
  • _ctype+1 เป็นตัวคำนวณเลขคณิตเพื่อรับที่อยู่ของรายการอาร์เรย์
  • [(unsigned char)_c]เป็นเพียงการเข้าถึงอาเรย์ของอาเรย์นั้นโดยที่นักแสดงอยู่ที่นั่นเพื่อบังคับใช้ข้อกำหนดมาตรฐานของพารามิเตอร์ที่สามารถแทนunsigned charได้ โปรดทราบว่าcharจริง ๆ แล้วสามารถเก็บค่าลบดังนั้นนี่คือการเขียนโปรแกรมป้องกัน ผลลัพธ์ของการ[]เข้าถึงอาร์เรย์คืออักขระเดียวจากตารางสัญลักษณ์ภายใน
  • การ&ปิดบังคือการรับกลุ่มอักขระบางอย่างจากตารางสัญลักษณ์ เห็นได้ชัดว่าตัวละครทุกตัวที่มีชุดบิต 5 (หน้ากาก 0x20) เป็นตัวควบคุม ไม่มีความเข้าใจในเรื่องนี้หากไม่ดูตาราง
  • อะไรก็ตามที่มีชุดบิต 5 จะส่งคืนค่าที่ปิดบังด้วย 0x20 ซึ่งเป็นค่าที่ไม่เป็นศูนย์ นี่เป็นความต้องการของฟังก์ชั่นที่ส่งกลับไม่ใช่ศูนย์ในกรณีของบูลีนจริง

มันไม่ถูกต้องที่นักแสดงจะต้องปฏิบัติตามข้อกำหนดมาตรฐานที่ค่านั้นสามารถแทนunsigned charได้ มาตรฐานกำหนดให้ค่า*เป็นตัวแทนได้unsigned charหรือเท่ากับEOFเมื่อเรียกรูทีน การคาสต์ทำหน้าที่เป็น“ การป้องกัน” การเขียนโปรแกรมเท่านั้น: การแก้ไขข้อผิดพลาดของโปรแกรมเมอร์ที่ผ่านการเซ็นชื่อchar(หรือ a signed char) เมื่อความรับผิดชอบอยู่บนพวกเขาเพื่อส่งผ่านunsigned charค่าเมื่อใช้ctype.hแมโคร มันควรจะตั้งข้อสังเกตนี้จะไม่สามารถแก้ไขข้อผิดพลาดเมื่อchar-1 ค่าของถูกส่งผ่านไปในการดำเนินงานที่ใช้ EOF-1
Eric Postpischil

+ 1แห่งนี้มีคำอธิบายที่ หากก่อนหน้านี้แมโครไม่ได้มีการปรับการป้องกันนี้ก็สามารถนำมาใช้เพียงเพราะ((_ctype_+1)[_c] & _C)มีตารางดัชนีที่มีการปรับค่าล่วงหน้า −1 ถึง 255 ดังนั้นรายการแรกไม่ได้ข้ามและมีวัตถุประสงค์ เมื่อมีคนเพิ่มการป้องกันในภายหลังEOFค่าของ −1 จะไม่ทำงานกับนักแสดงนั้นดังนั้นพวกเขาจึงเพิ่มผู้ปฏิบัติงานตามเงื่อนไขเพื่อปฏิบัติกับมันโดยเฉพาะ
Eric Postpischil

3

_ctype_เป็นตัวชี้ไปยังอาร์เรย์ทั่วโลก 257 ไบต์ ฉันไม่รู้ว่า_ctype_[0]ใช้อะไร _ctype_[1]ผ่าน_ctype_[256]_ตัวแทนของประเภทลักษณะของตัวละคร 0, ... , 255 ตามลำดับ: หมายถึงหมวดหมู่ของตัวละครตัวนี้_ctype_[c + 1] cนี่คือสิ่งเดียวกับที่บอกว่า_ctype_ + 1จุดไปยังอาร์เรย์ของ 256 ตัวอักษรที่(_ctype_ + 1)[c]แสดงให้เห็นถึง categorty cของตัวละคร

(_ctype_ + 1)[(unsigned char)_c]ไม่ใช่การประกาศ มันเป็นนิพจน์โดยใช้โอเปอเรเตอร์ตัวห้อย มันเข้าถึงตำแหน่งของอาร์เรย์ที่เริ่มต้นที่(unsigned char)_c(_ctype_ + 1)

บรรยากาศรหัส_cจากintการunsigned charที่ไม่จำเป็นอย่างเคร่งครัด: ฟังก์ชั่นใช้ ctype ใช้ค่าถ่านโยนไปunsigned char( charมีการลงนามใน OpenBSD): char c; … iscntrl((unsigned char)c)โทรที่ถูกต้องคือ พวกเขามีข้อได้เปรียบในการรับประกันว่าไม่มีบัฟเฟอร์ล้น: หากแอปพลิเคชันเรียกใช้iscntrlด้วยค่าที่อยู่นอกช่วงunsigned charและไม่ใช่ -1 ฟังก์ชันนี้จะส่งคืนค่าที่อาจไม่มีความหมาย แต่อย่างน้อยจะไม่ทำให้เกิด ความผิดพลาดหรือการรั่วไหลของข้อมูลส่วนตัวที่เกิดขึ้นกับที่อยู่นอกขอบเขตของอาเรย์ ค่านี้จะถูกต้องแม้กระทั่งถ้าฟังก์ชันนั้นถูกเรียกchar c; … iscntrl(c)ใช้ตราบเท่าที่cไม่เท่ากับ -1

เหตุผลสำหรับกรณีพิเศษกับ -1 EOFคือว่ามันเป็น หลายฟังก์ชั่นมาตรฐาน C ที่ทำงานบนcharเช่นgetcharเป็นตัวแทนของตัวละครตัวนี้เป็นintค่าซึ่งเป็นค่าถ่านห่อเป็นช่วงบวกและใช้ค่าพิเศษEOF == -1เพื่อแสดงให้เห็นว่าตัวละครไม่สามารถอ่าน สำหรับฟังก์ชั่นเช่นgetchar, EOFแสดงให้เห็นจุดสิ้นสุดของแฟ้มจึงชื่ออี nd- o F- ile Eric Postpischilแนะนำว่ารหัสนี้เป็นเพียงแค่เดิมreturn _ctype_[_c + 1]และนั่นอาจจะถูกต้อง: _ctype_[0]จะเป็นค่าสำหรับ EOF การนำไปใช้ที่ง่ายกว่านี้จะทำให้เกิดการโอเวอร์โฟลว์บัฟเฟอร์หากฟังก์ชั่นนั้นใช้ผิดวัตถุประสงค์ในขณะที่การนำไปใช้ในปัจจุบันจะหลีกเลี่ยงสิ่งนี้ตามที่กล่าวไว้ข้างต้น

หากvเป็นค่าที่พบในอาเรย์ให้v & _Cทดสอบว่าบิตที่0x20ตั้งอยู่vหรือไม่ ค่าในอาเรย์เป็นมาสก์ของหมวดหมู่ที่ตัวละครอยู่: _Cถูกตั้งค่าสำหรับตัวควบคุม, _Uตั้งค่าสำหรับตัวอักษรตัวพิมพ์ใหญ่, ฯลฯ


(_ctype_ + 1)[_c] จะใช้ดัชนีอาเรย์ที่ถูกต้องตามที่ระบุโดยมาตรฐาน C เพราะเป็นความรับผิดชอบของผู้ใช้ในการส่งEOFต่อunsigned charค่าใดค่าหนึ่ง พฤติกรรมสำหรับค่าอื่น ๆ ไม่ได้กำหนดโดยมาตรฐาน C การส่งไม่ได้ทำหน้าที่ในการใช้งานพฤติกรรมที่ต้องการโดยมาตรฐาน C มันเป็นวิธีแก้ปัญหาที่ใส่ในการป้องกันข้อผิดพลาดที่เกิดจากโปรแกรมเมอร์เขียนผิดค่าอักขระ อย่างไรก็ตามมันไม่สมบูรณ์หรือไม่ถูกต้อง (และไม่สามารถแก้ไขได้) เนื่องจากค่าของอักขระ −1 จะต้องได้รับการปฏิบัติเช่นEOFเดียวกัน
Eric Postpischil

+ 1แห่งนี้มีคำอธิบายที่ หากก่อนหน้านี้แมโครไม่ได้มีการปรับการป้องกันนี้ก็สามารถนำมาใช้เพียงเพราะ((_ctype_+1)[_c] & _C)มีตารางดัชนีที่มีการปรับค่าล่วงหน้า −1 ถึง 255 ดังนั้นรายการแรกไม่ได้ข้ามและมีวัตถุประสงค์ เมื่อมีคนเพิ่มการป้องกันในภายหลังEOFค่าของ −1 จะไม่ทำงานกับนักแสดงนั้นดังนั้นพวกเขาจึงเพิ่มผู้ปฏิบัติงานตามเงื่อนไขเพื่อปฏิบัติกับมันโดยเฉพาะ
Eric Postpischil

2

ฉันจะเริ่มต้นด้วยขั้นตอนที่ 3:

เพิ่มที่อยู่ของพอยน์เตอร์ที่ไม่ได้กำหนดชี้ไปที่ 1

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

แล้วมันจะชี้ไปยังอะไร?

มันชี้ไปที่อาร์เรย์ที่มีข้อมูลเกี่ยวกับตัวละครแต่ละตัว ตัวละครแต่ละตัวมีรายการของตัวเอง รายการเป็นการแทนค่าบิตแมปของลักษณะสำหรับอักขระ ตัวอย่างเช่น: หากตั้งค่าบิต 5 หมายความว่าอักขระนั้นเป็นอักขระควบคุม ตัวอย่างอื่น: หากตั้งค่าบิต 0 หมายความว่าอักขระนั้นเป็นอักขระตัวบน

ดังนั้นสิ่งที่ต้องการจะได้รับลักษณะที่นำไปใช้(_ctype_ + 1)['x'] 'x'จากนั้นใช้ค่าบิตและดำเนินการเพื่อตรวจสอบว่ามีการตั้งค่าบิต 5 หรือไม่เช่นตรวจสอบว่าเป็นอักขระควบคุมหรือไม่

เหตุผลในการเพิ่ม 1 อาจเป็นเพราะดัชนีจริง 0 ถูกสงวนไว้สำหรับวัตถุประสงค์พิเศษบางอย่าง


1

ข้อมูลทั้งหมดที่นี่ขึ้นอยู่กับการวิเคราะห์ซอร์สโค้ด (และประสบการณ์การเขียนโปรแกรม)

การประกาศ

extern const char *_ctype_;

บอกคอมไพเลอร์ที่มีความเป็นตัวชี้ไปยังที่แห่งหนึ่งชื่อconst char_ctype_

(4) ตัวชี้นี้ถูกเข้าถึงเป็นอาร์เรย์

(_ctype_ + 1)[(unsigned char)_c]

การโยน(unsigned char)_cทำให้แน่ใจว่าค่าดัชนีอยู่ในช่วงของunsigned char(0..255)

เลขคณิตของตัวชี้_ctype_ + 1เลื่อนตำแหน่งอาเรย์โดย 1 องค์ประกอบอย่างมีประสิทธิภาพ ฉันไม่รู้ว่าทำไมพวกเขาจึงใช้อาร์เรย์ในลักษณะนี้ การใช้ช่วง_ctype_[1].. _ctype[256]สำหรับค่าอักขระ0.. 255ทำให้ค่า_ctype_[0]ไม่ได้ใช้สำหรับฟังก์ชันนี้ (ชดเชย 1 สามารถนำมาใช้ในหลายทางเลือก)

การเข้าถึงอาร์เรย์เรียกใช้ค่า (ชนิดcharเพื่อประหยัดพื้นที่) โดยใช้ค่าอักขระเป็นดัชนีอาร์เรย์

(5) ค่าบิตและการดำเนินการแยกบิตเดียวจากค่า

เห็นได้ชัดว่ามีการใช้ค่าจากอาร์เรย์เป็นฟิลด์บิตโดยที่บิต 5 (นับจาก 0 เริ่มต้นที่บิตที่มีนัยสำคัญน้อย = 0x20) คือค่าสถานะสำหรับ "เป็นอักขระควบคุม" ดังนั้นอาร์เรย์จึงมีค่าฟิลด์บิตที่อธิบายคุณสมบัติของอักขระ


ผมคิดว่าพวกเขาย้าย+ 1ไปชี้ที่จะทำให้มันชัดเจนว่าพวกเขามีการเข้าถึงองค์ประกอบแทน1..256 จะได้รับเทียบเท่าเนื่องจากการแปลงนัยไป และจะยิ่งชัดเจนและกระชับยิ่งขึ้น 1..255,0_ctype_[1 + (unsigned char)_c]int_ctype_[(_c & 0xff) + 1]
cmaster - คืนสถานะโมนิกา

0

กุญแจสำคัญในที่นี้คือการทำความเข้าใจกับสิ่งที่นิพจน์(_ctype_ + 1)[(unsigned char)_c]ทำ (ซึ่งจะถูกป้อนให้กับbitwise และการดำเนินการ& 0x20เพื่อรับผล

คำตอบสั้น ๆ : มันกลับองค์ประกอบของอาร์เรย์ที่ชี้ไปโดย_c + 1_ctype_

อย่างไร?

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

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

#include <stdio.h>
int main() {
    // Code like the following two lines will be defined somewhere in the run-time
    // libraries with which your program is linked, only using _ctype_ in place of _qlist_ ...
    const char list[] = "abcdefghijklmnopqrstuvwxyz";
    const char* _qlist_ = list;
    // These two lines show how expressions like (a)[b] and (a+1)[b] just boil down to
    // a[b] and a[b+1], respectively ...
    char p = (_qlist_)[6];
    char q = (_qlist_ + 1)[6];
    printf("p = %c  q = %c\n", p, q);
    return 0;
}

อย่าลังเลที่จะขอคำอธิบายและ / หรือคำอธิบายเพิ่มเติม


0

ฟังก์ชั่นที่ประกาศในยอมรับวัตถุประเภทctype.h สำหรับตัวอักษรที่ใช้เป็นข้อโต้แย้งมันจะสันนิษฐานว่าพวกเขาเป็นเบื้องต้นหล่อพิมพ์int unsigned charตัวละครนี้ใช้เป็นดัชนีในตารางที่กำหนดคุณสมบัติของตัวละคร

ดูเหมือนว่าการตรวจสอบ_c == -1จะใช้ในกรณีเมื่อมีค่าของ_c EOFถ้ามันไม่ได้EOFแล้ว _C _ctype_ + 1จะหล่อไปถ่านที่ไม่ได้ลงชื่อชนิดที่ใช้เป็นดัชนีในตารางที่ชี้ไปตามการแสดงออก และถ้าบิตที่ระบุโดยหน้ากาก0x20ถูกตั้งค่าตัวละครเป็นสัญลักษณ์ควบคุม

เพื่อให้เข้าใจถึงการแสดงออก

(_ctype_ + 1)[(unsigned char)_c]

พิจารณาว่าการห้อยแถวลำดับเป็นตัวดำเนินการ postfix ที่กำหนดไว้

postfix-expression [ expression ]

คุณอาจไม่ชอบเขียน

_ctype_ + 1[(unsigned char)_c]

เพราะการแสดงออกนี้เทียบเท่า

_ctype_ + ( 1[(unsigned char)_c] )

ดังนั้นนิพจน์_ctype_ + 1นั้นอยู่ในวงเล็บเพื่อให้ได้นิพจน์หลัก

ดังนั้นในความเป็นจริงคุณมี

pointer[integral_expression]

ที่อัตราผลตอบแทนวัตถุของอาร์เรย์ที่ดัชนีที่คำนวณจากการแสดงออกintegral_expressionที่เป็นตัวชี้(_ctype_ + 1)(เกียร์จะใช้ตัวชี้ arithmetuc) และที่เป็นดัชนีการแสดงออกintegral_expression(unsigned char)_c

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