size_t หรือ int สำหรับส่วนข้อมูลดัชนี ฯลฯ


15

ใน C ++, size_t(หรืออย่างถูกต้องมากขึ้นT::size_typeซึ่งเป็น "มักจะ" size_t; เช่นunsignedประเภท) จะใช้เป็นค่าตอบแทนสำหรับsize()อาร์กิวเมนต์เพื่อoperator[]ฯลฯ (ดูstd::vector, et. al.)

ในทางตรงกันข้ามภาษา. NET ใช้int(และเลือกlong) เพื่อจุดประสงค์เดียวกัน ในความเป็นจริงที่สอดคล้องกับ CLS ภาษาไม่จำเป็นต้องสนับสนุนประเภทที่ไม่ได้ลงชื่อ

เนื่องจาก. NET นั้นใหม่กว่า C ++ มีบางสิ่งที่บอกฉันว่าอาจมีปัญหาในการใช้งานunsigned intแม้กระทั่งสิ่งที่ "ไม่สามารถ" เป็นลบได้เช่นดัชนีอาร์เรย์หรือความยาว C ++ เป็น "วัตถุทางประวัติศาสตร์" สำหรับความเข้ากันได้แบบย้อนหลังหรือไม่? หรือมีการแลกเปลี่ยนการออกแบบที่แท้จริงและสำคัญระหว่างสองแนวทาง

เหตุใดเรื่องนี้ อืม ... ฉันควรใช้อะไรกับคลาสหลายมิติใหม่ใน C ++; size_tหรือint?

struct Foo final // e.g., image, matrix, etc.
{
    typedef int32_t /* or int64_t*/ dimension_type; // *OR* always "size_t" ?
    typedef size_t size_type; // c.f., std::vector<>

    dimension_type bar_; // maybe rows, or x
    dimension_type baz_; // e.g., columns, or y

    size_type size() const { ... } // STL-like interface
};

6
น่าสังเกตว่า: ในหลาย ๆ ที่ใน. NET Framework -1จะถูกส่งคืนจากฟังก์ชั่นที่คืนค่าดัชนีเพื่อระบุว่า "ไม่พบ" หรือ "อยู่นอกช่วง" มันกลับมาจากCompare()ฟังก์ชั่น (การนำไปใช้IComparable) int 32 บิตถูกพิจารณาว่าเป็นประเภทสำหรับหมายเลขทั่วไปสำหรับสิ่งที่ฉันหวังว่าเป็นเหตุผลที่ชัดเจน
Robert Harvey

คำตอบ:


9

ระบุว่า. NET นั้นใหม่กว่า C ++ มีบางอย่างบอกฉันว่าอาจมีปัญหาในการใช้ int ที่ไม่ได้ลงนามแม้กระทั่งกับสิ่งที่ "ไม่สามารถ" อาจเป็นค่าลบเช่นดัชนีอาร์เรย์หรือความยาว

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

sum = data[k - 2] + data[k - 1] + data[k] + data[k + 1] + ...

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

if (k - 2 < 0) {
    throw std::out_of_range("will never be thrown"); 
}

if (k < 2) {
    throw std::out_of_range("will be thrown"); 
}

if (k < 2uL) {
    throw std::out_of_range("will be thrown, without signedness ambiguity"); 
}

แต่คุณต้องจัดเรียงนิพจน์การตรวจสอบช่วงของคุณใหม่ นั่นคือความแตกต่างหลัก โปรแกรมเมอร์ต้องจำกฎการแปลงจำนวนเต็มด้วย หากมีข้อสงสัยให้อ่านhttp://en.cppreference.com/w/cpp/language/operator_arithmetic#Conversionsอีกครั้ง

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

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

C ++ นั้นแตกต่างกันและยากที่จะได้รับรหัสที่ถูกต้อง

เกี่ยวกับความสำคัญในทางปฏิบัติของการอนุญาตให้เลขคณิตที่ลงนามเพื่อลบการละเมิดที่อาจเกิดขึ้นของ "หลักการของความประหลาดใจน้อย" กรณีในจุดคือ OpenCV ซึ่งใช้จำนวนเต็ม 32 บิตลงนามสำหรับดัชนีองค์ประกอบเมทริกซ์ขนาดอาร์เรย์จำนวนพิกเซลช่องทาง ฯลฯ การประมวลผลเป็นตัวอย่างของการเขียนโปรแกรมโดเมนที่ใช้ดัชนีอาเรย์แบบสัมพัทธ์อย่างหนัก อันเดอร์โฟลว์จำนวนเต็มที่ไม่ได้ลงนาม (ผลลบล้อมรอบ) จะทำให้การใช้งานอัลกอริทึมมีความซับซ้อนอย่างรุนแรง


นี่คือสถานการณ์ของฉัน ขอบคุณสำหรับตัวอย่างที่เฉพาะเจาะจง (ใช่ฉันรู้เรื่องนี้ แต่อาจมีประโยชน์หากมี "เจ้าหน้าที่ระดับสูง" เพื่อกล่าวถึง)
Ðаn

1
@ แดน: หากคุณต้องการอ้างถึงบางสิ่งบางอย่างโพสต์นี้จะดีกว่า
ร. ต.

1
@Dan: John Regehr กำลังค้นคว้าปัญหานี้ในภาษาการเขียนโปรแกรม ดูblog.regehr.org/archives/1401
rwong

มีความคิดเห็นที่แตก: gustedt.wordpress.com/2013/07/15/…
rwong

14

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

size_t เป็นขนาดจำนวนเต็มโดยมีวัตถุประสงค์:

ชนิดsize_tเป็นชนิดจำนวนเต็มแบบไม่มีเครื่องหมายที่กำหนดไว้ซึ่งมีขนาดใหญ่พอที่จะมีขนาดเป็นไบต์ของวัตถุใด ๆ (ข้อกำหนด C ++ 11 18.2.6)

ดังนั้นเวลาที่คุณต้องการในการทำงานที่มีขนาดของวัตถุในไบต์ที่คุณควรsize_tใช้ ในหลายกรณีคุณไม่ได้ใช้ส่วนข้อมูล / ดัชนีเหล่านี้เพื่อนับจำนวนไบต์ แต่นักพัฒนาส่วนใหญ่เลือกที่จะใช้ที่size_tนั่นเพื่อความสอดคล้อง

โปรดทราบว่าคุณควรเสมอใช้size_tถ้าชั้นเรียนของคุณมีจุดมุ่งหมายที่จะมีรูปลักษณ์และความรู้สึกของชั้น STL ทั้งหมดในชั้นเรียน STL size_tในการใช้งานสเปค มันถูกต้องสำหรับการรวบรวมเพื่อ typedef size_tจะเป็นunsigned intและก็ยังถูกต้องมันจะ typedefed unsigned longไป หากคุณใช้intหรือlongโดยตรงคุณจะพบกับคอมไพเลอร์ซึ่งบุคคลที่คิดว่าคลาสของคุณตามสไตล์ของ STL จะติดกับดักเพราะคุณไม่ได้ทำตามมาตรฐาน

สำหรับการใช้ประเภทที่เซ็นชื่อมีข้อดีบางประการ:

  • ชื่อสั้น - เป็นเรื่องง่ายสำหรับคนที่จะประเภทแต่ยากมากที่จะถ่วงรหัสด้วยintunsigned int
  • หนึ่งจำนวนเต็มสำหรับแต่ละขนาด - มีจำนวนเต็มที่สอดคล้องกับ CLS เพียงหนึ่งเดียวคือ 32- บิตซึ่งก็คือ Int32 ใน C ++ มีสอง ( int32_tและuint32_t) สิ่งนี้ทำให้การทำงานร่วมกันของ API ง่ายขึ้น

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

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

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


สมมติว่าการดำเนินการของsize()ถูกreturn bar_ * baz_;; ตอนนี้ไม่ได้สร้างปัญหาที่อาจเกิดขึ้นกับจำนวนเต็มล้น (ล้อมรอบ) ที่ฉันจะไม่ได้ถ้าฉันไม่ได้ใช้size_t?
13аn

5
@Dan คุณสามารถสร้างกรณีเช่นนี้ที่มี ints ไม่ได้ลงนามจะมีความสำคัญและในกรณีเหล่านั้นดีที่สุดในการใช้คุณสมบัติภาษาเต็มรูปแบบในการแก้ไขมัน อย่างไรก็ตามฉันต้องบอกว่ามันเป็นสิ่งก่อสร้างที่น่าสนใจที่จะมีคลาสที่bar_ * baz_สามารถล้นจำนวนเต็มที่ลงนาม แต่ไม่ใช่จำนวนเต็มที่ไม่ได้ลงนาม การ จำกัด ตัวเองให้อยู่ใน C ++ เป็นเรื่องน่าสังเกตว่ามีการกำหนดโอเวอร์โฟลที่ไม่ได้ลงนามไว้ในสเป็ค แต่การโอเวอร์โฟลว์ที่ลงชื่อแล้วนั้นเป็นพฤติกรรมที่ไม่ได้กำหนดดังนั้นถ้าต้องการคำนวณเลขคณิตของจำนวนเต็ม
Cort Ammon - Reinstate Monica

1
@Dan - หากโอเวอร์โฟลว์ของsize()การคูณที่ลงชื่อแล้วคุณอยู่ในภาษา UB land (และในfwrapvโหมดดูถัดไป :) เมื่อถึงตอนนั้นมีเพียงเล็กน้อยกระจ้อยร่อยมากขึ้นมันล้นการคูณที่ไม่ได้ลงนามคุณในดินแดนรหัสผู้ใช้ - รหัส - คุณจะกลับมาขนาดปลอม ดังนั้นฉันจึงไม่คิดว่าจะซื้อป้ายที่นี่
Martin Ba

4

ฉันคิดว่าคำตอบของ rwong ด้านบนเน้นประเด็นไปแล้วอย่างยอดเยี่ยม

ฉันจะเพิ่ม 002 ของฉัน:

  • size_tนั่นคือขนาดที่ ...

    สามารถเก็บขนาดสูงสุดของวัตถุที่เป็นไปได้ในทางทฤษฎีทุกประเภท (รวมถึงอาร์เรย์)

    ... เป็นสิ่งจำเป็นสำหรับดัชนีช่วงเมื่อsizeof(type)==1กล่าวคือถ้าคุณกำลังจัดการกับชนิดไบต์ ( char) (แต่เราทราบว่ามันสามารถมีขนาดเล็กกว่าประเภท PTR :

  • ดังนั้นxxx::size_typeสามารถใช้ในกรณี 99.9% แม้ว่าจะเป็นประเภทขนาดที่เซ็นชื่อ (เปรียบเทียบssize_t)
  • ความจริงที่ว่าstd::vectorและเพื่อนเลือกsize_tผู้ที่ไม่ได้ลงชื่อชนิดขนาดและการจัดทำดัชนีมีการพิจารณาโดยบางที่จะเป็นข้อบกพร่องในการออกแบบ ฉันเห็นด้วย (ใช้เวลา 5 นาทีอย่างจริงจังและดูสายฟ้าพูดคุย CppCon 2559: Jon Kalb“ ไม่ได้ลงนาม: แนวทางสำหรับรหัสที่ดีกว่า” )
  • เมื่อคุณออกแบบ C ++ API วันนี้คุณอยู่ในที่แคบ: ใช้size_tเพื่อให้สอดคล้องกับ Standard Library หรือใช้ ( เซ็นชื่อ ) intptr_tหรือssize_tใช้สำหรับการคำนวณดัชนีบั๊กได้ง่ายและน้อย
  • อย่าใช้ int32 หรือ int64 - การใช้งานถ้าคุณต้องการที่จะไปลงนามและต้องการเครื่องขนาดคำหรือการใช้งานintptr_tssize_t

เพื่อตอบคำถามโดยตรงไม่ใช่ทั้งหมด "สิ่งประดิษฐ์ทางประวัติศาสตร์" เนื่องจากปัญหาทางทฤษฎีของความต้องการที่อยู่มากกว่าครึ่งหนึ่งของ ("การจัดทำดัชนี" หรือ) พื้นที่ที่อยู่ต้องเป็น aehm กล่าวอย่างใดในภาษาระดับต่ำเช่น C ++

ในความเข้าใจย้อนหลังฉันเองคิดว่ามันเป็นข้อบกพร่องในการออกแบบที่ Standard Library ใช้ไม่ได้ลงนามsize_tทั่วสถานที่แม้ว่ามันจะไม่ได้เป็นตัวแทนขนาดหน่วยความจำดิบ แต่ความจุของข้อมูลที่พิมพ์เช่นคอลเลกชัน:

  • กฎการส่งเสริมการขายจำนวนเต็ม C ++ s ->
  • ประเภทที่ไม่ได้ลงชื่อไม่ได้สร้างผู้สมัครที่ดีสำหรับประเภท "semantic" สำหรับบางอย่างเช่นขนาดที่ไม่ได้ลงนามในเชิงความหมาย

ฉันจะทำซ้ำคำแนะนำของจอนที่นี่:

  • เลือกประเภทสำหรับการทำงานที่รองรับ (ไม่ใช่ช่วงของค่า) (* 1)
  • อย่าใช้ประเภทที่ไม่ได้ลงชื่อใน API ของคุณ สิ่งนี้ซ่อนข้อบกพร่องโดยไม่มีประโยชน์กลับหัว
  • อย่าใช้ "ไม่ได้ลงนาม" สำหรับปริมาณ (* 2)

(* 1) ie unsigned == bitmask ไม่เคยทำคณิตศาสตร์เลย (นี่เป็นข้อยกเว้นครั้งแรก - คุณอาจต้องมีตัวนับที่ล้อมรอบ - นี่ต้องเป็นประเภทที่ไม่ได้ลงนาม)

(* 2) ปริมาณหมายถึงสิ่งที่คุณนับและ / หรือทำคณิตศาสตร์


คุณหมายถึงอะไรกับ "หน่วยความจำแบนเต็มตัว avilable"? นอกจากนี้แน่ใจว่าคุณไม่ต้องการssize_tกำหนดเป็นจี้ลงนามsize_tแทนintptr_tซึ่งสามารถเก็บตัวชี้ (ไม่ใช่สมาชิก -) ใด ๆ และอาจจะใหญ่กว่า?
Deduplicator

@Deduplicator - เอาล่ะฉันคิดว่าฉันอาจได้size_tคำจำกัดความที่ยุ่งเหยิงเล็กน้อย ดูsize_t vs. intptrและen.cppreference.com/w/cpp/types/size_t เรียนรู้สิ่งใหม่ในวันนี้ :-) ฉันคิดว่าส่วนที่เหลือของข้อโต้แย้งฉันจะดูว่าฉันสามารถแก้ไขประเภทที่ใช้
Martin Ba

0

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

ใช้ int ที่ลงนามแล้ว:

int32_t i = GetRandomNumberFromRange(-1000, 1000);

if (i < 0)
{
    //error
}

if (i > size())
{
    //error
}

ใช้ int ที่ไม่ได้ลงชื่อ:

int32_t i = GetRandomNumberFromRange(-1000, 1000);

/// This will underflow any number below zero, so that it becomes a very big *positive* number instead.
uint32_t asUnsigned = static_cast<uint32_t>(i);

/// We now don't need to check for below zero, since an unsigned integer can only be positive.
if (asUnsigned > size())
{
    //error
}

1
คุณจริงๆต้องการที่จะอธิบายว่าหนึ่งอย่างทั่วถึงมากขึ้น
Martin Ba

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

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