حجم الأنواع العددية الصحيحة
الأنواع التالية هي أنواع عددية صحيحة:
-
char
- الأنواع العددية الصحيحة المُؤشّرة Signed integer types
- الأنواع العددية الصحيحة غير المُؤشّرة Unsigned integer types
-
char16_t
وchar32_t
-
bool
-
wchar_t
باستثناء sizeof(char)
و sizeof(signed char)
و sizeof(unsigned char)
، الموجودة بين الفقرة 3.9.1.1 [basic.fundamental/1] والفقرة 5.3.3.1 [expr.sizeof] و sizeof(bool)
، والتي تتوقف على التنفيذ بالكامل وليس لها حدّ أدنى، فإنّّ متطلّبات الحد الأدنى لأحجام هذه الأنواع موجود في القسم 3.9.1 [basic.fundamental] من المعيار، وسنوضّحه أدناه.
حجم char
تحدّد جميع إصدارات معيار C++، في الفقرة 5.3.3.1، أنّ sizeof
تعيد القيمة 1
لكلّ من unsigned char
و signed char
و char
(مسألة ما إذا كان النوع char
مؤشّرا - signed
- أو غير مؤشّر - unsigned
- تتعلّق بالتنفيذ).
الإصدار ≥ C++ 14
النوع char
كبير بما يكفي لتمثيل 256 قيمة مختلفة، وهو مناسب لتخزين وحدات رموز UTF-8.
حجم الأنواع العددية الصحيحة المُؤشّرة وغير المُؤشّرة
تنصّ المواصفات القياسية، في الفقرة 3.9.1.2، أنّه في قائمة أنواع الأعداد الصحيحة القياسية المُؤشّرة، التي تتكون من signed char
و short int
و int
و long int
و long long int
، فإنّّ كل نوع سيوفّر مساحة تخزينية تكافئ على الأقل المساحة التخزينيّة للنّوع السابق في القائمة. إضافة لذلك، وكما هو مُوضّح في الفقرة 3.9.1.3، كل نوع من هذه الأنواع يقابله نوع صحيح قياسي غير مُؤشّر، هذه الأنواع هي:
-
unsigned char
-
unsigned short int
-
unsigned int
-
unsigned long int
-
unsigned long long int
هذه الأنواع في النهاية لها نفس حجم ومُحاذاة النوع المؤشّر المقابل بالإضافة إلى ذلك، وكما هو مُوضّح في الفقرة 3.9.1.1، النوع char
له نفس متطلّبات signed char
و unsigned char
فيما يخصّ الحجم والمحاذاة.
الإصدار < C++ 11
قبل الإصدار C++ 11، لم يكن long long
و unsigned long long
جزءًا من معيار C++. لكن بعد إدخالهما في لغة C في معيار C99، دعمت العديدُ من المصرّفات النوع long long
كنوع عددي صحيح مؤشّر، و unsigned long long
كنوع عددي صحيح غير مؤشّر موسّع، له نفس قواعد أنواع C.
يضمن المعيار ما يلي:
1 == sizeof(char) == sizeof(signed char) == sizeof(unsigned char) <= sizeof(short) == sizeof(unsigned short) <= sizeof(int) == sizeof(unsigned int) <= sizeof(long) == sizeof(unsigned long)
الإصدار ≥ C++ 11
<= sizeof(long long) == sizeof(unsigned long long)
لا ينصّ المعيار على حد أدنى للأحجام لكل نوع على حدة. بدلاً من ذلك، لكلّ نوعٍ من الأنواع مجالًا من الحدود الدنيا التي يمكن أن يدعمها، والتي تكون موروثة على النحو المُوضّح في الفقرة 3.9.1.3 من معيار C، في الفقرة 1.2.4.2.1. ويمكن استنتاج الحد الأدنى لحجم نوع ما من ذلك المجال من خلال تحديد الحد الأدنى لعدد البتّات المطلوبة، لاحظ أنّه في معظم الأنظمة قد يكون المجال المدعوم الفعلي لأيّ نوع أكبر من الحد الأدنى، وأنه في الأنواع المُؤشّرة تتوافق المجالات مع مكمّل (complement) واحد، وليس مع مكمّلين اثنين كما هو شائع؛ وذلك لتسهيل توافق مجموعة واسعة من المنصات مع المعايير.
النوع | النطاق الأدنى | العدد الأدنى المطلوب للبِتَّات |
---|---|---|
signed char
|
-127 إلى 127 | 8 |
unsigned char
|
0 إلى 255 | 8 |
signed short
|
-32,767 إلى 32,767 | 16 |
unsigned short
|
0 إلى 65,535 | 16 |
signed int
|
-32,767 إلى 32,767 | 16 |
unsigned int
|
0 إلى 65,535 | 16 |
signed long
|
-2,147,483,647 إلى 2,147,483,647 | 32 |
unsigned long
|
0 إلى 4,294,967,295 | 32 |
الإصدار ≥ C++ 11
النوع | النطاق الأدنى | العدد الأدنى المطلوب للبِتَّات |
---|---|---|
signed long long
|
-9,223,372,036,854,775,807 إلى 9,223,372,036,854,775,807 | 64 |
unsigned long long
|
0 إلى 18,446,744,073,709,551,615 | 64 |
قد تختلف أحجام الأنواع من تنفيذ لآخر بما أنه يُسمح للأنواع أن تكون أكبر من الحد الأدنى لمتطلّبات الحجم، وأبرز مثال على ذلك تجده في نموذَجي البيانات المخزّنة على 64 بتّة: LP64 و LLP64، إذ أنّه في أنظمة LLP64 (مثل Windows 64-bit)، فإنّ الأنواع ints
و long
تُخزّن على 32 بتّة (32-bit)، وفي LP64 (مثل 64-bit Linux)، فإنّ int
مخزّنة على 32 بتّة، أمّا long
فمُخزّنة على 64 بتّة. لهذا من الخطأ افتراض أنّ أنواع الأعداد الصحيحة لها نفس الحجم في جميع الأنظمة.
الإصدار ≥ C++ 11
إذا كانت هناك حاجة إلى أنواع عددية صحيحة ذات أحجام ثابتة فاستخدم أنواعًا من الترويسة
-
int8_t
-
int16_t
-
int32_t
-
int64_t
-
intptr_t
-
uint8_t
-
uint16_t
-
uint32_t
-
uint64_t
-
uintptr_t
الإصدار ≥ C++ 11
حجم char16_t و char32_t
يتعلق حجم char16_t
و char32_t
بالتنفيذ كما ينصّ على ذلك المعيار في الفقرة 5.3.3.1، مع الشروط الواردة في الفقرة 3.9.1.5:
-
char16_t
كبير بما يكفي لتمثيل أيّ وحدة رمز من UTF-16، كما أنّ له نفس الحجم والإشارة والمحاذاة التي للنوعuint_least16_t
، وعليه فحجمه يساوي 16 بتّة على الأقل. -
char32_t
كبير بما يكفي لتمثيل أي وحدة رمز في UTF-32، كما أنّ له نفس الحجم والإشارة والمحاذاة التي للنوعuint_least32_t
، وعليه فيجب أن يساوي حجمه 32 بتّة على الأقل.
حجم bool
يتعلق حجم bool
بالتنفيذ، ولا يساوي بالضرورة 1
.
حجم wchar_t
wchar_t
، كما هو مُوضّح في الفقرة 3.9.1.5 هو نوع متميّز إذ يمكن أن يمثّل مجال قيمه كل وحدات الرموز (code unit) في أكبر مجموعة محارف موسّعة من بين اللغات المدعومة، وله نفس الحجم والإشارة والمحاذاة لأحد الأنواع العددية الصحيحة الأخرى، والذي يمثّل نوعه الأساسي (underlying type).
يتعلق حجم هذا النوع بالتنفيذ كما هو مُعرَّف في الفقرة 5.3.3.1، وقد يساوي على سبيل المثال 8 أو 16 أو 32 بتّة على الأقل؛ وإذا كان النظام يدعم اليونيكود فينبغي أن يُخزّن النوع wchar_t
على 32 بتّة على الأقل (باستثناء Windows، إذ يُخزّن النّوع wchar_t
على 16 بتّة لأغراض التوافق). وهو موروث من معيار C90، في ISO 9899: 1990 الفقرة 4.1.5، لكن مع بعض التعديلات البسيطة.
ويساوي حجم wchar_t
غالبًا 8 أو 16 أو 32 بتّة، وذلك حسب التنفيذ. مثلًا:
-
في أنظمة يونكس والأنظمة المشابهة لها، تُخزّن
wchar_t
على 32 بتّة، وعادة ما تستخدم في UTF-32. -
في ويندوز، تُخزّن
wchar_t
على 16 بتّة، وتُستخدم مع UTF-16. -
على الأنظمة التي لا تدعم إلّا 8 بتّات فقط، تُخزّن
wchar_t
على 8 بتّات.
الإصدار ≥ C++ 11
إذا كنت تريد دعم اليونيكود، فإنّه يُوصى باستخدام char
لأجل الترميز UTF-8، و char16_t
لأجل الترميز UTF-16، و char32_t
لأجل الترميز UTF-32، بدلاً من استخدام wchar_t
.
نماذج البيانات
يمكن أن تختلف أحجام أنواع الأعداد الصحيحة بين المنصات كما ذكرنا أعلاه، وما يلي هي أكثر النماذج شيوعًا (الأحجام محسوبة بالبتّات):
النموذج |
int
|
long
|
مؤشر |
---|---|---|---|
LP32 (2/4/4) | 16 | 32 | 32 |
ILP32 (4/4/4) | 32 | 32 | 32 |
LLP64 (4/4/8) | 32 | 32 | 64 |
LP64 (4/8/8) | 32 | 64 | 64 |
من بين هذه النماذج:
- نظام ويندوز 16-بت استخدم LP32.
- الأنظمة الشبيهة بيونكس مثل يونكس ولينكس ونظام ماك 10 (Mac OSX) وغيرها، ذات معمارية 32 منها، وكذلك نظام ويندوز 32-بت، تستخدم جميعها ILP32.
- ويندوز 64-bit يستخدم LLP64.
- اليونكسات ذات معمارية 64-bit تستخدم LP64.
لاحظ أنّ هذه النماذج غير مذكورة في المعيار.
النوع Char قد يكون مُؤشَّرًا أو لا
لا ينصّ المعيار على وجوب أن يكون النوع char
مؤشَّرًا من عدمه، لذا فإنّّ كل مصرّف ينفذه بشكل مختلف، أو قد يسمح بتعديله باستخدام سطر الأوامر.
مجالات الأنواع العددية
تتعلّق مجالات أنواع الأعداد الصحيحة بالتنفيذ، كما توفّر الترويسة std::numeric_limits<T>
الذي يوفّر الحدّين الأدنى والأقصى لقيم جميع الأنواع الأساسية.
تفي القيم بالضمانات التي يحدّدها معيار C عبر الترويسات
-
std::numeric_limits<signed char>::min()
- تساويSCHAR_MIN
، وهي أصغر من أو تساوي -127. -
std::numeric_limits::max() تساوي SCHAR_MAX
، وهي أكبر من أو تساوي 127. -
std::numeric_limits<unsigned char>::max()
تساويUCHAR_MAX
، وهي أكبر من أو تساوي 255. -
std::numeric_limits<short>::min()
تساويSHRT_MIN
، وهي أصغر من -32767 أو تساويها. -
std::numeric_limits<short>::max()
تساويSHRT_MAX
، وهي أكبر من أو تساوي 32767. -
std::numeric_limits<unsigned short>::max()
تساويUSHRT_MAX
، وهي أكبر من أو تساوي 65535. -
std::numeric_limits<int>::min()
تساويINT_MIN
، وهي أصغر من -32767 أو تساويها. -
std::numeric_limits<int>::max()
تساويINT_MAX
، وهي أكبر من أو تساوي 32767. -
std::numeric_limits<unsigned int>::max()
تساويUINT_MAX
، وهي أكبر من أو تساوي 65535. -
std::numeric_limits<long>::min()
تساويLONG_MIN
، وهي أصغر من أو تساوي -2147483647. -
std::numeric_limits<long>::max()
تساويLONG_MAX
، وهي أكبر من أو تساوي 2147483647. -
std::numeric_limits<unsigned long>::max()
تساوي ULONG_MAX، وهي أكبر من أو تساوي 4294967295.
الإصدار ≥ C++11
-
std::numeric_limits<long long>::min()
تساويLLONG_MIN
، وهي أكبر من أو تساوي -9223372036854775807. -
std::numeric_limits<long long>::max()
تساويLLONG_MAX
، وهي أكبر من أو تساوي 9223372036854775807. -
std::numeric_limits<unsigned long long>::max()
تساويULLONG_MAX
، وهي أكبر من أو تساوي 18446744073709551615.
بالنسبة للنوع العشري T
، فإنّّ max()
تمثّل القيمة المنتهية (finite) القصوى، بينما تمثّل min()
الحدّ الأدنى للقيمة المُوحّدة الموجبة.
تمّ توفير بعض الأعضاء الإضافيين للأنواع العشرية، وهي متعلّقة بالتنفيذ أيضًا، ولكنّها تلبّي بعض الضمانات التي يحدّدها المعيار C عبر الترويسة
-
يعيد
digits10
عدد الأرقام العشرية الخاصّة بالدقة. -
std::numeric_limits<float>::digits10
تساويFLT_DIG
، والتي لا تقلّ عن 6. -
std::numeric_limits<double>::digits10
تساويDBL_DIG
، والتي لا تقلّ عن 10. -
std::numeric_limits<long double>::digits10
تساويLDBL_DIG
، والتي لا تقلّ عن 10. -
العضو
min_exponent10
هو الحد الأدنى السلبي E بحيث يكون 10 أسّ E طبيعيًّا. -
std::numeric_limits<float>::min_exponent10
تساويFLT_MIN_10_EXP
، والتي يساوي على الأكثر -37. -
std::numeric_limits<double>::min_exponent10
تساويDBL_MIN_10_EXP
، والتي تساوي على الأكثر -37. -
std::numeric_limits<long double>::min_exponent10
تساويLDBL_MIN_10_EXP
، والتي تساوي على الأكثر -37. -
العضو
max_exponent10
هو الحد الأقصى E بحيث يكون 10 أسّ E منتهيًا (finite). -
std::numeric_limits<float>::max_exponent10
يساويFLT_MIN_10_EXP
، ولا يقل عن 37. -
std::numeric_limits<double>::max_exponent10
يساويDBL_MIN_10_EXP
، ولا يقل عن 37. -
std::numeric_limits<long double>::max_exponent10
تساويLDBL_MIN_10_EXP
، ولا يقل عن37. -
إذا كان العضو
is_iec559
صحيحًا، فإنّّ النوع سيكون مطابقًا للمواصفات IEC 559 / IEEE 754، وبالتالي سيُحدَّد مجاله من قبل المعيار.
تمثيل قيم الأنواع العشرية
ينصّ المعيار أن لا تقلّ دقة النوع long double
عن دقة النوع double
، والذي ينبغي ألّا تقل دقّته عن دقّة النوع float
؛ وأنّ النوع long double
ينبغي أن يكون قادرًا على تمثيل أيّ قيمة يمثّلها النوع double
، وأن يمثّل double
أيّ قيمة يمكن أن يمثّلها النوع float
، أما تفاصيل التمثيل فتتعلّق بالتنفيذ.
وبالنسبة لنوع عشري T
، فإنّّ std::numeric_limits<T>::radix
تحدّد الجذر المُستخدم في تمثيل T
، وإذا كانت std::numeric_limits<T>::is_iec559
صحيحة، فإنّّ تمثيل T
يطابق أحد التنسيقات المُعرَّفة من قبل معيار IEC 559 / IEEE 754.
التدفق الزائد عند التحويل من عدد صحيح إلى غد صحيح مُؤشّر
عند تحويل عدد صحيح مؤشّر أو غير مؤشّر إلى نوع عددي صحيح مؤشّر ولا تكون قيمته قابلة للتمثيل في النوع المقصود، فإنّّ القيمة المُنتجة تتعلّق بالتنفيذ. مثلًا، لنفرض أن مجال النوع signed char
في هذا التنفيذ يكون [-128,127]، ومجال النوع unsigned char
من 0 حتى 255:
int x = 12345; signed char sc = x; // معرفة بالتنفيذ sc قيمة unsigned char uc = x; // مهيأة عند القيمة 57 uc
النوع الأساسي وحجم التعدادات
إذا لم يكن النوع الأساسي (underlying type) لنوع تعدادي مُحدّدا بشكل صريح، فإنّّه سيُعرَّف من قبل التنفيذ.
enum E { RED, GREEN, BLUE, }; using T = std::underlying_type<E>::type; // يعرِّفه التنفيذ
ومع ذلك، فإنّّ المعيار ينصّ على ألّا يكون نوع التعداد الأساسي أكبر من int
إلّا إن لم يكن النوعان int
و unsigned
int
قادريْن على تمثيل جميع قيم التعداد. لذلك في الشيفرة أعلاه، النوع T
يمكن أن يكون int
أو unsigned int
أو short
ولكن ليس long long
مثلًا. لاحظ أنّ التعداد له نفس حجم نوعه الأساسي (كما يعيده التابع sizeof
).
القيمة العددية لمؤشر
نتيجة تحويل مؤشّر إلى عدد صحيح باستخدام reinterpret_cast
تتعلّق بالتنفيذ، لكن "… يُهدف إلى ألا تكون النتيجة مفاجئة للمطوّرين الذين يعرفون نظام العنونة في الجهاز."
int x = 42; int *p = &x; long addr = reinterpret_cast<long> (p); std::cout << addr << "\n"; // طبع رقم عنوان
وبالمثل، فإنّّ المُؤشر الناتج عن تحويل عدد صحيح يكون أيضًا متعلّقًا بالتنفيذ، والطريقة الصحيحة لتخزين مؤشّر كعدد صحيح هي استخدام النوعين uintptr_t
أو intptr_t
، انظر المثال التالي حيث لم يكن uintptr_t
في C++03 وإنما في C99، كنوع اختياري في الترويسة
#include <stdint.h> uintptr_t uip;
الإصدار ≥ C++ 11 يوجد std::uintptr_t
اختياري في C++11:
#include <cstdint> std::uintptr_t uip;
تُحيل C++ 11 إلى C99 لتعريف uintptr_t
(المعيار C99، 6.3.2.3):
اقتباسيتميّز النوع العددي الصحيح غير المُؤشّر، بإمكانية تحويل أيّ مؤشّر صالح يشير إلى
void
إليه، ثم إعادة تحويله مرّة أخرى إلى مؤشّر إلىvoid
، وتكون النتيجة مساوية للمؤشّر الأصلي.
بالنسبة لغالبية المنصات الحديثة، يمكنك أن تفترض أنّ مساحة العنونة (address space) مسطحة وأنّ الحسابيات على uintptr_t
تكافئ الحسابيات على char *
، ومن الممكن أن يجري التنفيذ أيّ تعديل عند تحويل void *
إلى uintptr_t
طالما أنّه يمكن عكس التعديل عند التحويل من uintptr_t
إلى void *
.
مسائل تقنية
-
في الأنظمة المتوافقة مع XSI (X/Open System Interfaces)، فإنّّ النوعين
intptr_t
وuintptr_t
إلزاميان، أمّا في الأنظمة الأخرى فهما اختياريان. -
الدوال ليست كائنات ضمن معيار C، ولا يضمن معيار C أنّ
uintptr_t
يستطيع تخزين مؤشّر دالة. كذلك يتطّلب التوافق مع POSIX (2.12.3) أنّ:
اقتباسيجب أن يكون لجميع أنواع مؤشّرات الدوالّ نفس تمثيل مؤشّر النوع الذي يشير إلى void، وينبغي ألا يغير تحويل مؤشّر دالّة إلى void * التمثيل، ويمكن إعادة تحويل قيمة void * الناتجة عن هذا التحويل إلى نوع مؤشّر الدالّة الأصلي، وذلك باستخدام تحويل صريح، دون الخشية من خسارة أيّ معلومات.
معيار C99 الفقرة 7.18.1:
اقتباسعندما تختلف typedef في غياب أو وجود الحرف الأوّل u، فيجب أن تشير إلى الأنواع المُؤشّرة وغير المُؤشّرة المقابلة كما هو مُوضّح في الفقرة 6.2.5؛ وأيّ تنفيذ يوفّر أحد هذين النوعين ينبغي أن يوفّر أيضًا النوع المقابل لآخر.
قد يكون uintptr_t
مناسبًا إذا كنت تريد العمل على بتّات المؤشّر بشكل قد يتعذّر في حال استخدمت عددًا صحيحًا مؤشّرًا.
عدد البتات في البايت
البايت (byte) في C++ هو المساحة التي يشغلها كائن char
، وعدد البتات في البايت مُحدّد في CHAR_BIT
، ومُعرّف في climits
ولا يقل عن 8. وفي حين أن عدد بتّات البايت في معظم الأنظمة الحديثة هو 8 وأن أنظمة POSIX تتطلّب أن يساوي CHAR_BIT
القيمة 8 بالضبط، إلا أنّ هناك بعض الأنظمة التي يكون فيها CHAR_BIT
أكبر من 8، فقد يساوي مثلًا 8 أو 16 أو 32 أو 64 بتّة.
هذا الدرس جزء من سلسلة دروس عن C++.
ترجمة -بتصرّف- للفصل Chapter 71: Implementation-defined behavior من كتاب C++ Notes for Professionals
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.