5. ข้อผิดพลาดทั่วไปเมื่อใช้อาร์เรย์
5.1 Pitfall: เชื่อใจในการเชื่อมโยงแบบไม่ปลอดภัย
ตกลงคุณได้รับการบอกกล่าวหรือพบตัวเองแล้วว่า globals (ตัวแปรขอบเขตเนมสเปซที่สามารถเข้าถึงได้นอกหน่วยการแปล) คือ Evil ™ แต่คุณรู้หรือไม่ว่า Evil ™เป็นอย่างไร พิจารณาโปรแกรมด้านล่างประกอบด้วยสองไฟล์ [main.cpp] และ [numbers.cpp]:
// [main.cpp]
#include <iostream>
extern int* numbers;
int main()
{
using namespace std;
for( int i = 0; i < 42; ++i )
{
cout << (i > 0? ", " : "") << numbers[i];
}
cout << endl;
}
// [numbers.cpp]
int numbers[42] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
ใน Windows 7 จะรวบรวมและเชื่อมโยงกับทั้ง MinGW g ++ 4.4.1 และ Visual C ++ 10.0
เนื่องจากประเภทไม่ตรงกันโปรแกรมจึงขัดข้องเมื่อคุณเรียกใช้
คำอธิบายแบบเป็นทางการ: โปรแกรมมีพฤติกรรมที่ไม่ได้กำหนด (UB) และแทนที่จะล้มเหลวจึงสามารถแขวนหรืออาจจะไม่ทำอะไรเลยหรือส่งอีเมลที่ส่งไปยังประธานาธิบดีของสหรัฐอเมริการัสเซียอินเดีย จีนและสวิตเซอร์แลนด์และทำให้ Nasal Daemons ลอยออกจากจมูกคุณ
คำอธิบายในการปฏิบัติ: ในmain.cpp
อาร์เรย์จะถือว่าเป็นตัวชี้วางไว้ที่ที่อยู่เดียวกับอาร์เรย์ สำหรับปฏิบัติการแบบ 32 บิตนี่หมายความว่าint
ค่าแรก
ในอาร์เรย์นั้นจะถือว่าเป็นตัวชี้ เช่นในตัวแปรมีหรือดูเหมือนจะมี, สิ่งนี้ทำให้โปรแกรมเข้าถึงหน่วยความจำลงที่ด้านล่างสุดของพื้นที่ที่อยู่ซึ่งสงวนไว้ตามปกติและทำให้เกิดกับดัก ผลลัพธ์: คุณได้รับความผิดพลาดmain.cpp
numbers
(int*)1
คอมไพเลอร์อยู่ภายใต้สิทธิ์อย่างเต็มที่ที่จะไม่วินิจฉัยข้อผิดพลาดนี้เนื่องจาก C ++ 11 §3.5 / 10 บอกว่าเกี่ยวกับข้อกำหนดของประเภทที่เข้ากันได้สำหรับการประกาศ
[N3290 §3.5 / 10]
การละเมิดกฎนี้กับข้อมูลประจำตัวของประเภทไม่จำเป็นต้องมีการวินิจฉัย
ย่อหน้าเดียวกันมีรายละเอียดเกี่ยวกับรูปแบบที่อนุญาต:
... การประกาศสำหรับวัตถุอาร์เรย์สามารถระบุประเภทของอาร์เรย์ที่แตกต่างกันโดยการมีหรือไม่มีของขอบเขตอาร์เรย์หลัก (8.3.4)
รูปแบบที่อนุญาตนี้ไม่รวมถึงการประกาศชื่อเป็นอาร์เรย์ในหนึ่งหน่วยการแปลและเป็นตัวชี้ในหน่วยการแปลอื่น
5.2 หลุมพราง: ทำการเพิ่มประสิทธิภาพก่อนวัยอันควร ( memset
& เพื่อน)
ยังไม่ได้เขียน
5.3 ข้อผิดพลาด: การใช้สำนวน C เพื่อรับจำนวนองค์ประกอบ
ด้วยประสบการณ์ C ลึกเป็นเรื่องธรรมดาที่จะเขียน ...
#define N_ITEMS( array ) (sizeof( array )/sizeof( array[0] ))
ตั้งแต่array
สูญสลายไปชี้ไปยังองค์ประกอบแรกที่จำเป็นต้องแสดงออกยังสามารถเขียนเป็นsizeof(a)/sizeof(a[0])
sizeof(a)/sizeof(*a)
มันมีความหมายเหมือนกันและไม่ว่ามันจะเขียนอย่างไรมันเป็นสำนวน Cในการค้นหาองค์ประกอบจำนวนของอาร์เรย์
ข้อผิดพลาดหลัก: สำนวน C ไม่ปลอดภัย ตัวอย่างเช่นรหัส ...
#include <stdio.h>
#define N_ITEMS( array ) (sizeof( array )/sizeof( *array ))
void display( int const a[7] )
{
int const n = N_ITEMS( a ); // Oops.
printf( "%d elements.\n", n );
}
int main()
{
int const moohaha[] = {1, 2, 3, 4, 5, 6, 7};
printf( "%d elements, calling display...\n", N_ITEMS( moohaha ) );
display( moohaha );
}
ส่งผ่านตัวชี้ไปที่N_ITEMS
และดังนั้นส่วนใหญ่จะสร้างผลลัพธ์ที่ผิด คอมไพล์เป็นไฟล์ปฏิบัติการแบบ 32 บิตใน Windows 7 มันสร้าง ...
7 องค์ประกอบการโทรแสดง ...
1 องค์ประกอบ
- คอมไพเลอร์ปรับเปลี่ยนเพียงแค่
int const a[7]
int const a[]
- คอมไพเลอร์ปรับเปลี่ยนไป
int const a[]
int const* a
N_ITEMS
ถูกเรียกใช้ด้วยตัวชี้
- สำหรับปฏิบัติการแบบ 32 บิต
sizeof(array)
(ขนาดของตัวชี้) เท่ากับ 4
sizeof(*array)
เทียบเท่ากับsizeof(int)
ซึ่งสำหรับการปฏิบัติการแบบ 32 บิตก็เช่นกัน
ในการตรวจสอบข้อผิดพลาดนี้ในเวลาทำงานคุณสามารถทำ ...
#include <assert.h>
#include <typeinfo>
#define N_ITEMS( array ) ( \
assert(( \
"N_ITEMS requires an actual array as argument", \
typeid( array ) != typeid( &*array ) \
)), \
sizeof( array )/sizeof( *array ) \
)
7 องค์ประกอบการโทรที่แสดง ...
การยืนยันล้มเหลว: ("N_ITEMS ต้องใช้อาร์เรย์จริงเป็นอาร์กิวเมนต์", typeid (a)! = typeid (& * a)), ไฟล์ runtime_detect ion.cpp, บรรทัดที่ 16
แอปพลิเคชั่นนี้ร้องขอให้ Runtime ทำการยกเลิกในลักษณะที่ผิดปกติ
โปรดติดต่อทีมสนับสนุนของแอปพลิเคชันสำหรับข้อมูลเพิ่มเติม
การตรวจจับข้อผิดพลาดรันไทม์นั้นดีกว่าการตรวจจับใด ๆ แต่มันเสียเวลาประมวลผลเพียงเล็กน้อยและอาจทำให้โปรแกรมเมอร์เสียเวลามากขึ้น ดีกว่าด้วยการตรวจจับในเวลารวบรวม! และถ้าคุณยินดีที่จะไม่สนับสนุนอาร์เรย์ประเภทท้องถิ่นด้วย C ++ 98 คุณสามารถทำได้ดังนี้:
#include <stddef.h>
typedef ptrdiff_t Size;
template< class Type, Size n >
Size n_items( Type (&)[n] ) { return n; }
#define N_ITEMS( array ) n_items( array )
การคอมไพล์คำจำกัดความนี้ถูกแทนที่ด้วยโปรแกรมที่สมบูรณ์แรกด้วย g ++, ฉันได้ ...
M: \ count> g ++ compile_time_detection.cpp
compile_time_detection.cpp: ในฟังก์ชั่น 'void display (const int *)':
compile_time_detection.cpp: 14: ข้อผิดพลาด: ไม่มีฟังก์ชันที่ตรงกันสำหรับการเรียกไปที่ 'n_items (const int * &)
M: \ count> _
วิธีการทำงาน: อาร์เรย์ถูกส่งผ่านโดยอ้างอิงถึงn_items
และดังนั้นจึงไม่สลายตัวไปยังตัวชี้ไปยังองค์ประกอบแรกและฟังก์ชั่นก็สามารถคืนจำนวนองค์ประกอบที่ระบุโดยประเภท
ด้วย C ++ 11 คุณสามารถใช้สิ่งนี้ได้เช่นกันสำหรับอาร์เรย์ของชนิดโลคัลและเป็นชนิดที่ปลอดภัย
C ++ สำนวนสำหรับการค้นหาจำนวนองค์ประกอบของอาร์เรย์
ข้อผิดพลาด 5.4 C ++ 11 และ C ++ 14 ข้อผิดพลาด: การใช้constexpr
ฟังก์ชันขนาดอาร์เรย์
ด้วย C ++ 11 และใหม่กว่านั้นเป็นเรื่องธรรมดา แต่คุณจะเห็นว่าอันตราย! เพื่อแทนที่ฟังก์ชั่น C ++ 03
typedef ptrdiff_t Size;
template< class Type, Size n >
Size n_items( Type (&)[n] ) { return n; }
กับ
using Size = ptrdiff_t;
template< class Type, Size n >
constexpr auto n_items( Type (&)[n] ) -> Size { return n; }
ที่การเปลี่ยนแปลงที่สำคัญคือการใช้constexpr
ซึ่งจะช่วยให้ฟังก์ชั่นนี้ในการผลิตคงที่รวบรวมเวลา
ตัวอย่างเช่นในทางตรงกันข้ามกับฟังก์ชั่น C ++ 03 ค่าคงที่เวลาการคอมไพล์ดังกล่าวสามารถใช้เพื่อประกาศอาร์เรย์ที่มีขนาดเดียวกันกับอีกขนาดหนึ่ง:
// Example 1
void foo()
{
int const x[] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 4};
constexpr Size n = n_items( x );
int y[n] = {};
// Using y here.
}
แต่พิจารณารหัสนี้โดยใช้constexpr
รุ่น:
// Example 2
template< class Collection >
void foo( Collection const& c )
{
constexpr int n = n_items( c ); // Not in C++14!
// Use c here
}
auto main() -> int
{
int x[42];
foo( x );
}
หลุมพราง: ณ เดือนกรกฎาคม 2558 ข้างต้นคอมไพล์ด้วย MinGW-64 5.1.0 ด้วย
-pedantic-errors
และการทดสอบกับคอมไพเลอร์ออนไลน์ที่gcc.godbolt.org/ , ด้วย clang 3.0 และ clang 3.2 แต่ไม่ใช่ clang 3.3, 3.4 1, 3.5.0, 3.5.1, 3.6 (rc1) หรือ 3.7 (ทดลอง) และที่สำคัญสำหรับแพลตฟอร์ม Windows นั้นไม่ได้คอมไพล์ด้วย Visual C ++ 2015 เหตุผลคือคำสั่ง C ++ 11 / C ++ 14 เกี่ยวกับการใช้การอ้างอิงในconstexpr
นิพจน์:
C ++ 11 C ++ 14 $ 5.19 / 2 เก้า
THรีบ
เงื่อนไขการแสดงออก e
เป็นแสดงออกคงหลักเว้นแต่การประเมินe
ตามกฎของเครื่องนามธรรม (1.9) จะประเมินหนึ่งของการแสดงออกดังต่อไปนี้:
⋮
- ID-แสดงออกที่หมายถึงตัวแปรหรือข้อมูลสมาชิกของชนิดการอ้างอิงเว้นแต่อ้างอิงมีการเริ่มต้นก่อนหน้านี้และทั้ง
- มันเริ่มต้นได้ด้วยการแสดงออกอย่างต่อเนื่องหรือ
- มันเป็นสมาชิกข้อมูลที่ไม่คงที่ของวัตถุที่อายุการใช้งานเริ่มขึ้นในการประเมิน e;
หนึ่งสามารถเขียน verbose มากขึ้น
// Example 3 -- limited
using Size = ptrdiff_t;
template< class Collection >
void foo( Collection const& c )
{
constexpr Size n = std::extent< decltype( c ) >::value;
// Use c here
}
… แต่สิ่งนี้ล้มเหลวเมื่อCollection
ไม่ใช่อาเรย์ดิบ
ในการจัดการกับคอลเลกชันที่ไม่ใช่อาร์เรย์เราต้องการความสามารถในการโอเวอร์โหลดของ
n_items
ฟังก์ชั่น แต่สำหรับการคอมไพล์เวลาต้องใช้การรวบรวมเวลาแทนขนาดอาเรย์ และคลาสสิก C ++ 03 วิธีการแก้ปัญหาซึ่งทำงานได้ดียังอยู่ใน C ++ 11 และ C ++ 14 คือการปล่อยให้รายงานผลการทำงานของตนไม่ได้เป็นค่าผ่านทาง แต่ผลการทำงานของประเภท ตัวอย่างเช่นนี้:
// Example 4 - OK (not ideal, but portable and safe)
#include <array>
#include <stddef.h>
using Size = ptrdiff_t;
template< Size n >
struct Size_carrier
{
char sizer[n];
};
template< class Type, Size n >
auto static_n_items( Type (&)[n] )
-> Size_carrier<n>;
// No implementation, is used only at compile time.
template< class Type, size_t n > // size_t for g++
auto static_n_items( std::array<Type, n> const& )
-> Size_carrier<n>;
// No implementation, is used only at compile time.
#define STATIC_N_ITEMS( c ) \
static_cast<Size>( sizeof( static_n_items( c ).sizer ) )
template< class Collection >
void foo( Collection const& c )
{
constexpr Size n = STATIC_N_ITEMS( c );
// Use c here
(void) c;
}
auto main() -> int
{
int x[42];
std::array<int, 43> y;
foo( x );
foo( y );
}
เกี่ยวกับตัวเลือกประเภทส่งคืนสำหรับstatic_n_items
: รหัสนี้ไม่ได้ใช้std::integral_constant
เนื่องจากstd::integral_constant
ผลลัพธ์จะแสดงเป็นconstexpr
ค่าโดยตรงเพื่อนำเสนอปัญหาเดิมอีกครั้ง แทนที่จะเป็นSize_carrier
คลาสหนึ่งสามารถให้ฟังก์ชันส่งคืนการอ้างอิงไปยังอาร์เรย์ได้โดยตรง อย่างไรก็ตามทุกคนไม่คุ้นเคยกับไวยากรณ์นั้น
เกี่ยวกับการตั้งชื่อ: ส่วนหนึ่งของโซลูชันนี้สำหรับปัญหาconstexpr
-invalid-Due-to-Reference คือการเลือกตัวเลือกการรวบรวมเวลาคงที่อย่างชัดเจน
หวังว่าปัญหา oops-there-was-a-reference-related-in-your- constexpr
จะได้รับการแก้ไขด้วย C ++ 17 แต่จนกระทั่งมาโครดังกล่าวSTATIC_N_ITEMS
ข้างต้นให้ความสะดวกในการพกพาเช่นคอมไพเลอร์ clang และ Visual C ++ ความปลอดภัย
ที่เกี่ยวข้อง: MYLIB_STATIC_N_ITEMS
แมโครไม่เคารพขอบเขตเพื่อที่จะหลีกเลี่ยงการชนชื่อมันอาจจะเป็นความคิดที่ดีที่จะใช้คำนำหน้าชื่อเช่น