เมื่อใดที่จะใช้ std :: size_t?


201

ฉันแค่สงสัยว่าฉันควรใช้std::size_tกับลูปและสิ่งของแทนได้intอย่างไร ตัวอย่างเช่น

#include <cstdint>

int main()
{
    for (std::size_t i = 0; i < 10; ++i) {
        // std::size_t OK here? Or should I use, say, unsigned int instead?
    }
}

โดยทั่วไปแล้ววิธีปฏิบัติที่ดีที่สุดเกี่ยวกับเวลาที่ใช้std::size_tคืออะไร

คำตอบ:


186

กฎที่ดีของหัวแม่มือคือสำหรับสิ่งที่คุณจะต้องเปรียบเทียบในสภาพที่ห่วงกับสิ่งที่เป็นธรรมชาติstd::size_tของตัวเอง

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

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


41
มันมูลค่าการกล่าวขวัญว่าไม่ได้ใช้size_tเมื่อคุณควรจะนำไปสู่ข้อบกพร่องการรักษาความปลอดภัย
BlueRaja - Danny Pflughoeft

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

2
@JoSo นอกจากนี้ยังมีssize_tสำหรับค่าลงนาม
EntangledLoops

70

size_tเป็นประเภทผลลัพธ์ของsizeofโอเปอเรเตอร์

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

นอกจากนี้การใช้size_tเพื่อแสดงขนาดเป็นไบต์ช่วยให้สามารถพกพาโค้ดได้


32

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

for (size_t i = 0, max = strlen (str); i < max; i++)
    doSomethingWith (str[i]);

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

แต่ระวังสิ่งต่าง ๆ เช่น:

for (size_t i = strlen (str) - 1; i >= 0; i--)

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

for (size_t i = strlen (str); i-- > 0; )

โดยขยับพร่องลงในโพสต์ตรวจสอบผลข้างเคียงของสภาพความต่อเนื่องนี้จะตรวจสอบความต่อเนื่องกับค่าก่อนที่จะลดลง แต่ยังคงใช้ค่า decremented ภายในวง (ซึ่งเป็นเหตุผลที่วิ่งออกจากห่วงlen .. 1มากกว่าlen-1 .. 0)


14
โดยวิธีการที่มันเป็นวิธีที่ไม่ดีที่จะเรียกstrlenการวนซ้ำในแต่ละครั้ง :) คุณสามารถทำสิ่งนี้:for (size_t i = 0, len = strlen(str); i < len; i++) ...
musiphil

1
แม้ว่าจะเป็นประเภทที่มีการลงชื่อคุณต้องระวังเงื่อนไขขอบเขตอาจจะมากกว่านั้นเนื่องจากการล้นจำนวนเต็มที่ลงนามแล้วนั้นเป็นพฤติกรรมที่ไม่ได้กำหนด
Adrian McCarthy

2
การนับถอยหลังอย่างถูกต้องสามารถทำได้ในวิธีต่อไปนี้ (เสียชื่อ):for (size_t i = strlen (str); i --> 0;)
Jo ดังนั้น

1
@ โจโซนั่นเป็นกลอุบายที่ค่อนข้างเรียบร้อย แต่ฉันไม่แน่ใจว่าฉันชอบการแนะนำตัวดำเนินการ-->"ไปที่" (ดูstackoverflow.com/questions/1642028/… ) รวมคำแนะนำของคุณไว้ในคำตอบแล้ว
paxdiablo

ที่คุณสามารถทำได้ง่ายๆif (i == 0) break;ในตอนท้ายของการห่วง (เช่นfor (size_t i = strlen(str) - 1; ; --i)(ผมเช่นคุณดีกว่าว่า แต่แค่สงสัยว่านี้จะทำงานก็เช่นกัน)..
RastaJedi

13

โดยคำจำกัดความsize_tเป็นผลมาจากsizeofผู้ประกอบการ size_tถูกสร้างเพื่ออ้างถึงขนาด

จำนวนครั้งที่คุณทำบางสิ่งบางอย่าง (10 ในตัวอย่างของคุณ) ไม่เกี่ยวกับขนาดเพื่อให้การใช้งานว่าทำไมsize_t? intหรือunsigned intควรจะโอเค

แน่นอนว่ามันเกี่ยวข้องกับสิ่งที่คุณทำiในวง หากคุณส่งผ่านไปยังฟังก์ชั่นที่รับunsigned intเช่นเลือกunsigned intตัวอย่างเช่นเลือก

ไม่ว่าในกรณีใดฉันแนะนำให้หลีกเลี่ยงการแปลงประเภทโดยนัย ทำให้การแปลงประเภททั้งหมดชัดเจน


10

size_tเป็นวิธีที่อ่านได้ง่ายมากในการระบุขนาดของรายการ - ความยาวของสตริงจำนวนไบต์ที่ตัวชี้ใช้เป็นต้นนอกจากนี้ยังสามารถพกพาข้ามแพลตฟอร์มได้ - คุณจะพบว่า 64 บิตและ 32 บิตทำงานได้ดีกับระบบของระบบและsize_t- สิ่งที่unsigned intอาจไม่ทำ (เช่นเมื่อใดควรใช้unsigned long


9

คำตอบสั้น ๆ :

แทบจะไม่เคย

คำตอบยาว:

เมื่อใดก็ตามที่คุณต้องมีเวกเตอร์ของถ่านที่ใหญ่กว่านั้น 2gb ในระบบ 32 บิต ในทุกกรณีการใช้งานการใช้ประเภทที่เซ็นชื่อนั้นปลอดภัยกว่าการใช้ประเภทที่ไม่ได้ลงนาม

ตัวอย่าง:

std::vector<A> data;
[...]
// calculate the index that should be used;
size_t i = calc_index(param1, param2);
// doing calculations close to the underflow of an integer is already dangerous

// do some bounds checking
if( i - 1 < 0 ) {
    // always false, because 0-1 on unsigned creates an underflow
    return LEFT_BORDER;
} else if( i >= data.size() - 1 ) {
    // if i already had an underflow, this becomes true
    return RIGHT_BORDER;
}

// now you have a bug that is very hard to track, because you never 
// get an exception or anything anymore, to detect that you actually 
// return the false border case.

return calc_something(data[i-1], data[i], data[i+1]);

ลงนามเทียบเท่าsize_tเป็นไม่ได้ptrdiff_t intแต่intในกรณีส่วนใหญ่ยังใช้งานได้ดีกว่า size_t ptrdiff_tคือlongในระบบ 32 และ 64 บิต

ซึ่งหมายความว่าคุณต้องแปลงเป็นและจาก size_t ทุกครั้งที่คุณโต้ตอบกับ std :: container ซึ่งไม่สวยงามมาก แต่ในการประชุมที่กำลังจะเกิดขึ้นผู้เขียน c ++ กล่าวว่าการออกแบบ std :: vector ด้วยขนาดที่ไม่ได้ลงนามนั้นเป็นความผิดพลาด

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

calc_something(data[size_t(i-1)], data[size_t(i)], data[size_t(i+1)]);

ถ้าเพียงแค่ต้องการย้ำคอลเลคชั่นโดยไม่มีการ จำกัด ขอบเขตให้ใช้ช่วงสำหรับ:

for(const auto& d : data) {
    [...]
}

ต่อไปนี้เป็นคำบางคำจาก Bjarne Stroustrup (ผู้เขียน C ++) ที่เป็นเจ้าของภาษา

สำหรับบางคนข้อผิดพลาดในการออกแบบที่ลงชื่อ / ไม่ได้ลงชื่อใน STL นั้นมีเหตุผลเพียงพอที่จะไม่ใช้ std :: vector แต่เป็นการใช้งานของตัวเองแทน


1
ผมเข้าใจว่าพวกเขามาจาก for(int i = 0; i < get_size_of_stuff(); i++)แต่ผมยังคิดว่ามันแปลกที่จะเขียน ตอนนี้แน่นอนคุณอาจไม่ต้องการทำลูปดิบจำนวนมาก แต่ - มาเลยคุณก็ใช้มัน
einpoklum

เหตุผลเดียวที่ฉันใช้ลูปแบบดิบคือเนื่องจากไลบรารีอัลกอริทึม c ++ ออกแบบมาไม่ดี มีภาษาเช่น Scala ที่มีไลบรารีที่พัฒนาและดีกว่ามากในการทำงานกับคอลเล็กชัน จากนั้นกรณีการใช้งานของลูปดิบจะถูกกำจัดออกไป นอกจากนี้ยังมีวิธีการปรับปรุง c ++ ด้วย STL ใหม่และดีกว่า แต่ฉันสงสัยว่าจะเกิดขึ้นภายในทศวรรษหน้า
Arne

1
ฉันได้รับที่ไม่ได้ลงนาม i = 0; ยืนยัน (i-1, MAX_INT); แต่ฉันไม่เข้าใจว่าทำไมคุณถึงพูดว่า "ถ้าฉันมีอันเดอร์โฟลว์อยู่แล้วสิ่งนี้จะกลายเป็นจริง" เพราะพฤติกรรมของเลขคณิตใน ints ที่ไม่ได้ลงนามนั้นจะถูกกำหนดไว้เสมอเช่น ผลลัพธ์เป็นผลลัพธ์โมดูโลขนาดของจำนวนเต็มที่เป็นตัวแทนที่ใหญ่ที่สุด ดังนั้นถ้าฉัน == 0 ดังนั้นฉัน - กลายเป็น MAX_INT จากนั้น i ++ กลายเป็น 0 อีกครั้ง
mabraham

@Mabraham ฉันดูอย่างระมัดระวังและคุณพูดถูกรหัสของฉันไม่ได้ดีที่สุดที่จะแสดงปัญหา โดยปกติจะx + 1 < yเทียบเท่ากับx < y - 1แต่ไม่ได้อยู่กับจำนวนเต็ม unsigend ที่สามารถแนะนำข้อผิดพลาดได้อย่างง่ายดายเมื่อสิ่งต่าง ๆ ถูกเปลี่ยนให้ถือว่าเท่าเทียมกัน
Arne

8

ใช้ std :: size_t สำหรับการทำดัชนี / นับอาร์เรย์ C-style

สำหรับคอนเทนเนอร์ STL คุณจะมี (ตัวอย่าง) vector<int>::size_typeซึ่งควรใช้สำหรับการทำดัชนีและการนับองค์ประกอบเวกเตอร์

ในทางปฏิบัติพวกเขามักจะเป็นทั้ง ints ที่ไม่ได้ลงชื่อ แต่ไม่รับประกันโดยเฉพาะเมื่อใช้ตัวจัดสรรแบบกำหนดเอง


2
ด้วย gcc บน linux std::size_tโดยปกติแล้วunsigned long(8 ไบต์บนระบบ 64 บิต) แทนที่จะเป็นunisgned int(4 ไบต์)
rafak

5
size_tแม้ว่าอาร์เรย์แบบ C นั้นไม่ได้จัดทำดัชนีไว้เนื่องจากดัชนีสามารถเป็นค่าลบได้ หนึ่งสามารถใช้size_tสำหรับอินสแตนซ์ของตัวเองของอาร์เรย์เช่นถ้าใครไม่ต้องการไปลบ
Johannes Schaub - litb

การเปรียบเทียบกับ u64s เร็วกว่าการเปรียบเทียบกับ u32s หรือไม่? ฉันได้กำหนดบทลงโทษด้านประสิทธิภาพอย่างรุนแรงสำหรับการใช้ u8s และ u16s เป็น sentinels แบบวนซ้ำ แต่ฉันไม่รู้ว่า Intel ได้รับการกระทำร่วมกันในยุค 64 หรือไม่
Crashworks

2
เนื่องจากการทำดัชนีอาเรย์แบบ C นั้นเทียบเท่ากับการใช้โอเปอเรเตอร์+กับพอยน์เตอร์จึงดูเหมือนว่าptrdiff_tเป็นดัชนีที่ใช้สำหรับดัชนี
Pavel Minaev

8
สำหรับvector<T>::size_type(และเหมือนกันสำหรับภาชนะอื่น ๆ ทั้งหมด) มันค่อนข้างไร้ประโยชน์จริง ๆ เพราะมันรับประกันได้อย่างมีประสิทธิภาพว่าsize_t- มันถูกพิมพ์ลงไปAllocator::size_typeและสำหรับข้อ จำกัด เกี่ยวกับภาชนะบรรจุดูที่ 20.1.5 / 4 - โดยเฉพาะsize_typeต้อง เป็นsize_tและจะต้องเป็นdifference_type ptrdiff_tแน่นอนค่าเริ่มต้นเป็นstd::allocator<T>ไปตามข้อกำหนดเหล่านั้น ดังนั้นเพียงแค่ใช้สั้นsize_tและไม่รำคาญกับส่วนที่เหลือของจำนวนมาก :)
พาเวล Minaev

7

ในไม่ช้าคอมพิวเตอร์ส่วนใหญ่จะเป็นสถาปัตยกรรม 64- บิตที่มีระบบปฏิบัติการ 64 บิต: เป็นโปรแกรมที่ทำงานบนภาชนะที่มีองค์ประกอบหลายพันล้านรายการ จากนั้นคุณต้องใช้size_tแทนintดัชนีลูปมิฉะนั้นดัชนีของคุณจะล้อมรอบที่องค์ประกอบ 2 ^ 32: th ทั้งบนระบบ 32- และ 64- บิต

เตรียมพร้อมสำหรับอนาคต!


อาร์กิวเมนต์ของคุณเพียงไปไกลเท่าที่ความหมายหนึ่งความต้องการมากกว่าlong int intหากsize_tเกี่ยวข้องกับระบบปฏิบัติการ 64 บิตแสดงว่ามีความเกี่ยวข้องกับระบบปฏิบัติการ 32 บิต
einpoklum

4

เมื่อใช้ size_t ให้ระวังด้วยนิพจน์ต่อไปนี้

size_t i = containner.find("mytoken");
size_t x = 99;
if (i-x>-1 && i+x < containner.size()) {
    cout << containner[i-x] << " " << containner[i+x] << endl;
}

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

if ((int)(i-x) > -1 or (i-x) >= 0)

ทั้งสองวิธีควรทำงาน นี่คือการทดสอบการทำงานของฉัน

size_t i = 5;
cerr << "i-7=" << i-7 << " (int)(i-7)=" << (int)(i-7) << endl;

ผลลัพธ์: i-7 = 18446744073709551614 (int) (i-7) = - 2

ฉันต้องการความคิดเห็นของผู้อื่น


2
โปรดทราบว่า(int)(i - 7)เป็นอันเดอร์โฟล์ที่ถูกโยนไปintหลังจากนั้นในขณะที่int(i) - 7ไม่ได้เป็น underflow ตั้งแต่คุณแปลงแรกiไปยังและจากนั้นลบint 7นอกจากนี้ฉันพบว่าตัวอย่างของคุณเกิดความสับสน
hochl

ประเด็นของฉันคือ int มักจะปลอดภัยกว่าเมื่อคุณลบออก
Kemin Zhou

4

size_t ถูกส่งคืนโดยไลบรารีต่าง ๆ เพื่อระบุว่าขนาดของคอนเทนเนอร์นั้นไม่ใช่ศูนย์ คุณใช้มันเมื่อคุณกลับมาอีกครั้ง: 0

อย่างไรก็ตามในตัวอย่างของคุณด้านบนวนลูปใน size_t เป็นข้อผิดพลาดที่อาจเกิดขึ้น พิจารณาสิ่งต่อไปนี้:

for (size_t i = thing.size(); i >= 0; --i) {
  // this will never terminate because size_t is a typedef for
  // unsigned int which can not be negative by definition
  // therefore i will always be >= 0
  printf("the never ending story. la la la la");
}

การใช้จำนวนเต็มที่ไม่ได้ลงนามมีศักยภาพในการสร้างปัญหาที่ละเอียดเหล่านี้ ดังนั้นฉันจึงชอบใช้ size_t เฉพาะเมื่อฉันโต้ตอบกับคอนเทนเนอร์ / ประเภทที่ต้องการ


ดูเหมือนว่า Everone จะใช้ size_t ในลูปโดยไม่ต้องกังวลเกี่ยวกับข้อผิดพลาดนี้และฉันได้เรียนรู้วิธีการนี้อย่างหนัก
Pranjal Gupta

-2

size_tเป็นประเภทที่ไม่ได้ลงนามที่สามารถเก็บค่าจำนวนเต็มสูงสุดสำหรับสถาปัตยกรรมของคุณดังนั้นจึงได้รับการคุ้มครองจากจำนวนเต็มล้นเนื่องจากการลงนาม (การ0x7FFFFFFFเพิ่มint ที่ลงนามโดย 1 จะทำให้คุณ -1) หรือขนาดสั้น 0)

ส่วนใหญ่จะใช้ในการทำดัชนีอาร์เรย์ / ลูป / ที่อยู่ทางคณิตศาสตร์และอื่น ๆ ฟังก์ชั่นที่เหมือนmemset()กันยอมรับsize_tเฉพาะในทางทฤษฎีเพราะคุณอาจมีหน่วยความจำขนาดบล็อก2^32-1(บนแพลตฟอร์ม 32 บิต)

สำหรับลูปที่เรียบง่ายเช่นนั้นไม่รบกวนและใช้แค่ int


-3

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

บางฟังก์ชั่นคืนค่า size_t และคอมไพเลอร์ของคุณจะเตือนคุณหากคุณพยายามทำการเปรียบเทียบ

หลีกเลี่ยงสิ่งนั้นโดยใช้ประเภทข้อมูลที่ลงชื่อ / ไม่ได้ลงนามหรือพิมพ์ตัวอักษรเพื่อแฮ็คที่รวดเร็ว


4
ใช้งานได้เฉพาะในกรณีที่คุณต้องการหลีกเลี่ยงข้อบกพร่องและช่องโหว่ด้านความปลอดภัย
Craig McQueen

2
อาจไม่สามารถแสดงจำนวนเต็มที่มากที่สุดในระบบของคุณ
Adrian McCarthy

-4

size_t ไม่ได้ลงนาม int ดังนั้นเมื่อใดก็ตามที่คุณต้องการ int ที่ไม่ได้ลงชื่อคุณสามารถใช้มันได้

ฉันใช้มันเมื่อฉันต้องการระบุขนาดของอาเรย์, ตัวนับและ ...

void * operator new (size_t size); is a good use of it.

10
จริงๆแล้วมันไม่จำเป็นต้องเหมือนกับ int ที่ไม่ได้ลงชื่อ มันเป็นไม่ได้ลงนาม แต่มันอาจจะมีขนาดใหญ่ (หรือผมคิดว่ามีขนาดเล็ก แต่ผมไม่ทราบว่าของแพลตฟอร์มใด ๆ ที่เป็นความจริง) กว่า int
ทอดด์ Gamblin

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