تم التوقيع على التحويل غير الموقع في لغة C - هل هو آمن دائمًا؟

StackOverflow https://stackoverflow.com/questions/50605

  •  09-06-2019
  •  | 
  •  

سؤال

لنفترض أن لدي رمز C التالي.

unsigned int u = 1234;
int i = -5678;

unsigned int result = u + i;

ما هي التحويلات الضمنية التي تجري هنا، وهل هذا الرمز آمن لجميع قيم u و i؟(آمن، بمعنى أنه على الرغم من ذلك نتيجة في هذا المثال سوف يفيض إلى عدد موجب ضخم، يمكنني إعادته إلى رقم كثافة العمليات والحصول على النتيجة الحقيقية.)

هل كانت مفيدة؟

المحلول

اجابة قصيرة

لك i سوف يكون تحويلها إلى عدد صحيح غير موقع عن طريق الإضافة UINT_MAX + 1, ، فسيتم تنفيذ الإضافة بالقيم غير الموقعة، مما يؤدي إلى حجم كبير result (اعتمادا على قيم u و i).

اجابة طويلة

وفقًا لمعيار C99:

6.3.1.8 التحويلات الحسابية المعتادة

  1. إذا كان كلا المعاملين لهما نفس النوع، فلن تكون هناك حاجة إلى مزيد من التحويل.
  2. بخلاف ذلك، إذا كان كلا المعاملين يحتويان على أنواع أعداد صحيحة موقعة أو كلاهما يحتوي على أنواع أعداد صحيحة غير موقعة، فسيتم تحويل المعامل ذو نوع رتبة تحويل عدد صحيح أقل إلى نوع المعامل ذو رتبة أكبر.
  3. بخلاف ذلك، إذا كان المعامل الذي يحتوي على نوع عدد صحيح غير مُوقع له مرتبة أكبر أو مساوية لرتبة نوع المعامل الآخر، فسيتم تحويل المعامل الذي يحتوي على نوع عدد صحيح مُوقع إلى نوع المعامل الذي يحتوي على نوع عدد صحيح غير مُوقع.
  4. بخلاف ذلك، إذا كان نوع المعامل بنوع عدد صحيح موقّع يمكن أن يمثل كافة قيم نوع المعامل بنوع عدد صحيح غير موقّع، فسيتم تحويل المعامل بنوع عدد صحيح غير موقّع إلى نوع المعامل بنوع عدد صحيح غير موقّع.
  5. بخلاف ذلك، يتم تحويل كلا المعاملين إلى نوع عدد صحيح غير موقّع يتوافق مع نوع المعامل مع نوع عدد صحيح موقّع.

في حالتك، لدينا int واحد غير موقع (u) ووقعت (i).بالإشارة إلى (3) أعلاه، نظرًا لأن كلا المعاملين لهما نفس الرتبة، فإنك i سوف تحتاج إلى أن تكون تحويلها إلى عدد صحيح غير موقعة.

6.3.1.3 الأعداد الصحيحة الموقعة وغير الموقعة

  1. عندما يتم تحويل قيمة ذات نوع عدد صحيح إلى نوع عدد صحيح آخر غير _Bool، إذا كان من الممكن تمثيل القيمة بالنوع الجديد، فلن تتغير.
  2. بخلاف ذلك، إذا كان النوع الجديد غير موقع، فسيتم تحويل القيمة عن طريق إضافة أو طرح قيمة واحدة بشكل متكرر أكثر من الحد الأقصى للقيمة التي يمكن تمثيلها في النوع الجديد حتى تصبح القيمة في نطاق النوع الجديد.
  3. وإلا فسيتم توقيع النوع الجديد ولا يمكن تمثيل القيمة فيه؛إما أن تكون النتيجة محددة بالتنفيذ أو يتم رفع إشارة محددة بالتنفيذ.

والآن علينا أن نرجع إلى (٢) أعلاه.لك i سيتم تحويله إلى قيمة غير موقعة عن طريق الإضافة UINT_MAX + 1.لذا فإن النتيجة ستعتمد على الكيفية UINT_MAX يتم تعريفه على التنفيذ الخاص بك.سيكون كبيرًا، لكنه لن يفيض، للأسباب التالية:

6.2.5 (9)

لا يمكن أبدًا تجاوز السعة الحسابية التي تتضمن معاملات غير موقعة، لأن النتيجة التي لا يمكن تمثيلها بواسطة النوع الصحيح غير الموقع يتم تقليلها بمقدار الرقم الذي يكون أكبر من أكبر قيمة يمكن تمثيلها بواسطة النوع الناتج.

علاوة:التحويل الحسابي شبه WTF

#include <stdio.h>

int main(void)
{
  unsigned int plus_one = 1;
  int minus_one = -1;

  if(plus_one < minus_one)
    printf("1 < -1");
  else
    printf("boring");

  return 0;
}

يمكنك استخدام هذا الرابط لتجربة ذلك عبر الإنترنت: https://repl.it/repls/QuickWhimsicalBytes

علاوة:التأثير الجانبي للتحويل الحسابي

يمكن استخدام قواعد التحويل الحسابية للحصول على قيمة UINT_MAX عن طريق تهيئة قيمة غير موقعة ل -1, ، أي:

unsigned int umax = -1; // umax set to UINT_MAX

ويضمن أن يكون هذا قابلاً للنقل بغض النظر عن تمثيل الرقم الموقع للنظام بسبب قواعد التحويل الموضحة أعلاه.راجع سؤال SO هذا لمزيد من المعلومات: هل من الآمن استخدام -1 لتعيين كافة البتات على أنها صحيحة؟

نصائح أخرى

التحويل من الموقعة إلى غير الموقعة لا لا بالضرورة فقط قم بنسخ أو إعادة تفسير تمثيل القيمة الموقعة.نقلا عن معيار C (C99 6.3.1.3):

عندما يتم تحويل قيمة ذات نوع عدد صحيح إلى نوع صحيح آخر بخلاف _bool ، إذا كان يمكن تمثيل القيمة بالنوع الجديد ، فهي دون تغيير.

خلاف ذلك ، إذا كان النوع الجديد غير موقّع ، يتم تحويل القيمة عن طريق إضافة أو طرح واحدة بشكل متكرر أكثر من القيمة القصوى التي يمكن تمثيلها في النوع الجديد حتى تكون القيمة في نطاق النوع الجديد.

وإلا فسيتم توقيع النوع الجديد ولا يمكن تمثيل القيمة فيه؛إما أن تكون النتيجة محددة للتنفيذ أو يتم رفع إشارة محددة للتنفيذ.

بالنسبة للتمثيل المكمل للاثنين والذي أصبح عالميًا تقريبًا هذه الأيام، تتوافق القواعد مع إعادة تفسير البتات.لكن بالنسبة للتمثيلات الأخرى (العلامة والحجم أو تكملة الآحاد)، يجب أن يرتب تطبيق لغة C نفس النتيجة، مما يعني أن التحويل لا يمكنه نسخ البتات فقط.على سبيل المثال، (unsigned)-1 == UINT_MAX، بغض النظر عن التمثيل.

بشكل عام، يتم تعريف التحويلات في لغة C للعمل على القيم، وليس على التمثيلات.

للإجابة على السؤال الأصلي:

unsigned int u = 1234;
int i = -5678;

unsigned int result = u + i;

يتم تحويل قيمة i إلى int غير الموقعة، مما يؤدي إلى العائد UINT_MAX + 1 - 5678.ثم يتم إضافة هذه القيمة إلى القيمة غير الموقعة 1234، العائد UINT_MAX + 1 - 4444.

(على عكس التجاوز غير الموقع، يستدعي التجاوز الموقع سلوكًا غير محدد.يعد الالتفاف أمرًا شائعًا، ولكن لا يضمنه معيار C - ويمكن أن تؤدي تحسينات المترجم إلى إحداث فوضى في التعليمات البرمجية التي تقدم افتراضات غير مبررة.)

بالاشارة الى الكتاب المقدس:

  • تؤدي عملية الإضافة إلى تحويل int إلى int غير موقع.
  • بافتراض وجود تمثيل مكمل لشخصين وأنواع متساوية الحجم، فإن نمط البت لا يتغير.
  • التحويل من int غير الموقع إلى int الموقع يعتمد على التنفيذ.(ولكن ربما يعمل بالطريقة التي تتوقعها على معظم الأنظمة الأساسية هذه الأيام.)
  • تكون القواعد أكثر تعقيدًا بعض الشيء في حالة الجمع بين الأحجام الموقعة وغير الموقعة ذات الأحجام المختلفة.

عند إضافة متغير واحد غير موقّع ومتغير موقّع (أو أي عملية ثنائية)، يتم تحويل كلاهما ضمنيًا إلى غير موقّع، مما قد يؤدي في هذه الحالة إلى نتيجة ضخمة.

لذا فمن الآمن أن تكون النتيجة ضخمة وخاطئة، لكنها لن تنهار أبدًا.

عند التحويل من موقع إلى غير موقع هناك احتمالان.الأرقام التي كانت موجبة في الأصل تظل (أو يتم تفسيرها على أنها) نفس القيمة.سيتم الآن تفسير الرقم الذي كان سالبًا في الأصل على أنه أرقام موجبة أكبر.

كما تمت الإجابة عليه سابقًا، يمكنك التنقل ذهابًا وإيابًا بين الموقع وغير الموقع دون مشكلة.حالة الحدود للأعداد الصحيحة الموقعة هي -1 (0xFFFFFFFF).حاول الإضافة والطرح من ذلك وستجد أنه يمكنك التراجع وجعله صحيحًا.

ومع ذلك، إذا كنت ستقوم بالتبادل ذهابًا وإيابًا، فإنني أنصحك بشدة بتسمية المتغيرات الخاصة بك بحيث يكون نوعها واضحًا، على سبيل المثال:

int iValue, iResult;
unsigned int uValue, uResult;

من السهل جدًا تشتيت انتباهك بقضايا أكثر أهمية ونسيان أي متغير ونوع إذا تمت تسميتها دون تلميح.لا ترغب في الإرسال إلى غير موقع ثم استخدامه كفهرس مصفوفة.

ما هي التحويلات الضمنية التي تحدث هنا،

سيتم تحويلي إلى عدد صحيح غير موقع.

وهل هذا الرمز آمن لجميع قيم u وi؟

آمن بمعنى أن يكون محددًا جيدًا نعم (انظر https://stackoverflow.com/a/50632/5083516 ).

تتم كتابة القواعد بلغة المعايير التي يصعب قراءتها عادةً، ولكن أيًا كان التمثيل الذي تم استخدامه في العدد الصحيح الموقع، فإن العدد الصحيح غير الموقع سيحتوي على تمثيل مكمل للرقم 2.

ستعمل عمليات الجمع والطرح والضرب بشكل صحيح على هذه الأرقام مما يؤدي إلى ظهور عدد صحيح آخر غير موقّع يحتوي على رقم مكمل ثنائي يمثل "النتيجة الحقيقية".

سيكون للتقسيم والصب إلى أنواع صحيحة أكبر غير موقعة نتائج محددة جيدًا ولكن هذه النتائج لن تكون تمثيلات مكملة لـ "النتيجة الحقيقية".

(آمن، بمعنى أنه على الرغم من أن النتيجة في هذا المثال سوف تفيض إلى عدد موجب ضخم، إلا أنه يمكنني إعادتها إلى int والحصول على النتيجة الحقيقية.)

في حين يتم تحديد التحويلات من الموقعة إلى غير الموقعة بواسطة المعيار، يتم تحديد العكس من خلال التنفيذ، حيث يحدد كل من gcc وmsvc التحويل بحيث تحصل على "النتيجة الحقيقية" عند تحويل الرقم التكميلي 2 المخزن في عدد صحيح غير موقع إلى عدد صحيح موقّع .أتوقع أنك لن تجد سوى أي سلوك آخر على الأنظمة الغامضة التي لا تستخدم تكملة 2 للأعداد الصحيحة الموقعة.

https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html#Integers-implementation https://msdn.microsoft.com/en-us/library/0eex498h.aspx

الإجابات الرهيبة وافرة

أوزجور أوزسيتاك

عندما يتم إلقاؤك من التوقيع إلى غير موقعة (والعكس) ، لا يتغير التمثيل الداخلي للرقم.ما يتغير هو كيف يفسر المترجم بتات الإشارة.

هذا خطأ تماما.

ماتس فريدريكسون

عند إضافة أحد المتغيرات غير الموقعة والوقوف (أو أي عملية ثنائية) ، يتم تحويل كلاهما ضمنيًا إلى غير موقعة ، مما سيؤدي في هذه الحالة إلى نتيجة ضخمة.

وهذا أيضا خطأ.يمكن ترقية ints غير الموقعة إلى ints إذا كانت ذات دقة متساوية بسبب بتات الحشو في النوع غير الموقع.

smh

تتسبب عملية الإضافة الخاصة بك في تحويل int إلى int غير موقعة.

خطأ.ربما يفعل وربما لا.

التحويل من int غير موقعة إلى int الموقعة هو الاعتماد على التنفيذ.(لكنه ربما يعمل بالطريقة التي تتوقعها على معظم المنصات هذه الأيام.)

خطأ.إنه إما سلوك غير محدد إذا تسبب في تجاوز السعة أو تم الاحتفاظ بالقيمة.

مجهول

تم تحويل قيمة I إلى int غير موقعة ...

خطأ.يعتمد على دقة int بالنسبة إلى int غير الموقع.

تايلور برايس

كما تم الإجابة عليه سابقًا ، يمكنك الإلقاء على الموقّع وغير الموقّع دون مشكلة.

خطأ.تؤدي محاولة تخزين قيمة خارج نطاق عدد صحيح موقّع إلى سلوك غير محدد.

الآن أستطيع أخيرا الإجابة على السؤال.

إذا كانت دقة int مساوية لـ unsigned int، فسيتم ترقيتك إلى int موقع وستحصل على القيمة -4444 من التعبير (u+i).الآن، إذا كان لدينا أنا وأنت قيم أخرى، فقد تحصل على سلوك تجاوز وغير محدد ولكن مع هذه الأرقام الدقيقة ستحصل على -4444 [1].سيكون لهذه القيمة نوع int.لكنك تحاول تخزين هذه القيمة في int غير موقع بحيث سيتم بعد ذلك تحويلها إلى int غير موقع وستكون القيمة التي ستنتهي بها النتيجة هي (UINT_MAX+1) - 4444.

إذا كانت دقة int غير الموقعة أكبر من دقة int، فسيتم ترقية int الموقع إلى int غير موقع مما يؤدي إلى القيمة (UINT_MAX+1) - 5678 التي ستتم إضافتها إلى int 1234 الآخر غير الموقع.إذا كان لدينا أنا وأنت قيم أخرى، مما يجعل التعبير يقع خارج النطاق {0..UINT_MAX}، فستتم إضافة القيمة (UINT_MAX+1) أو طرحها حتى تقع النتيجة داخل النطاق {0..UINT_MAX) و لن يحدث أي سلوك غير محدد.

ما هي الدقة؟

تحتوي الأعداد الصحيحة على بتات الحشو، وبتات الإشارة، وبتات القيمة.الأعداد الصحيحة غير الموقعة لا تحتوي على علامة واضحة.يتم أيضًا ضمان عدم احتواء الحرف غير الموقع على أجزاء حشو.عدد بتات القيم التي يحتوي عليها عدد صحيح هو مقدار الدقة التي يتمتع بها.

[مسكتك]

لا يمكن استخدام حجم الماكرو للماكرو وحده لتحديد دقة عدد صحيح في حالة وجود بتات الحشو.ولا يجب أن يكون حجم البايت ثمانيًا (ثمانية بتات) كما هو محدد بواسطة C99.

[1] قد يحدث الفائض عند إحدى نقطتين.إما قبل الإضافة (أثناء الترقية) - عندما يكون لديك int غير موقع وهو كبير جدًا بحيث لا يمكن احتواؤه داخل int.قد يحدث تجاوز السعة أيضًا بعد الإضافة حتى لو كان int غير الموقع ضمن نطاق int، بعد الإضافة قد تستمر النتيجة في تجاوز السعة.


وفي ملاحظة أخرى، أنا طالب دراسات عليا حديث أحاول العثور على عمل ;)

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top