8-বিট পূর্ণসংখ্যা থেকে আমি 8 বিটের চেয়ে বড় আকারের কীভাবে পাই?


118

আমি এই ছোট্ট রত্নটির পিছনে লুকিয়ে থাকা একটি অত্যন্ত বাজে বাগটিকে সন্ধান করেছি। আমি সচেতন যে প্রতি সি ++ স্পেস অনুযায়ী, স্বাক্ষরিত ওভারফ্লোগুলি পূর্বনির্ধারিত আচরণ, তবে কেবল তখনই যখন ওভারফ্লো ঘটে যখন মানটি বিট-প্রস্থে প্রসারিত হয় sizeof(int)। আমি এটি বুঝতে পেরেছি, ইনক্রিমেন্ট charকরা কখনই ততক্ষণ সংজ্ঞায়িত আচরণ হওয়া উচিত নয় sizeof(char) < sizeof(int)। তবে এটি ব্যাখ্যা করে না যে কীভাবে cএকটি অসম্ভব মান পাচ্ছে । একটি 8-বিট পূর্ণসংখ্যা হিসাবে, কীভাবে cমানগুলি এর বিট-প্রস্থের চেয়ে বেশি ধরে রাখতে পারে?

কোড

// Compiled with gcc-4.7.2
#include <cstdio>
#include <stdint.h>
#include <climits>

int main()
{
   int8_t c = 0;
   printf("SCHAR_MIN: %i\n", SCHAR_MIN);
   printf("SCHAR_MAX: %i\n", SCHAR_MAX);

   for (int32_t i = 0; i <= 300; i++)
      printf("c: %i\n", c--);

   printf("c: %i\n", c);

   return 0;
}

আউটপুট

SCHAR_MIN: -128
SCHAR_MAX: 127
c: 0
c: -1
c: -2
c: -3
...
c: -127
c: -128  // <= The next value should still be an 8-bit value.
c: -129  // <= What? That's more than 8 bits!
c: -130  // <= Uh...
c: -131
...
c: -297
c: -298  // <= Getting ridiculous now.
c: -299
c: -300
c: -45   // <= ..........

আদর্শের উপর এটি পরীক্ষা করে দেখুন।


61
"আমি সচেতন যে প্রতি সি ++ স্পেস অনুযায়ী, স্বাক্ষরিত ওভারফ্লোগুলি অপরিশোধিত।" - ঠিক আছে। ভালো হবে, না শুধু মান undefined হয়, আচরণ করা হয়। শারীরিকভাবে অসম্ভব ফলাফল পাওয়ার জন্য উপস্থিতি একটি বৈধ পরিণতি।

@hvd আমি নিশ্চিত যে সাধারণ সি ++ বাস্তবায়ন কীভাবে এই আচরণের কারণ ঘটায় সে সম্পর্কে কারও কাছে ব্যাখ্যা রয়েছে। সম্ভবত এটি সারিবদ্ধকরণের সাথে সম্পর্কিত বা printf()রূপান্তর কীভাবে হয়?
rliu

অন্যরা মূল বিষয়টি সম্বোধন করেছেন। আমার মন্তব্যটি আরও সাধারণ এবং ডায়াগনস্টিক পদ্ধতির সাথে সম্পর্কিত। আপনি কেন এই ধরণের ধাঁধা পেয়েছিলেন তার একটি অংশ আমি বিশ্বাস করি যে এটি বিশ্বাসযোগ্য নয় j স্পষ্টতই, এটি অসম্ভব নয়, তাই এটি গ্রহণ করুন এবং আবার দেখুন
টিম এক্স

@ টিমএক্স - আমি আচরণটি পর্যবেক্ষণ করেছিলাম এবং স্পষ্টতই এই সিদ্ধান্তে পৌঁছেছি যে এটি অর্থে অসম্ভব নয়। আমার এই শব্দটির ব্যবহারটি একটি 8-বিট পূর্ণসংখ্যাকে 9-বিট মান ধারণ করে, যা সংজ্ঞা দ্বারা অসম্ভব। এই ঘটনাটি ঘটেছিল তা বোঝায় যে এটি একটি 8-বিট মান হিসাবে বিবেচিত হচ্ছে না। অন্যরা যেমন সম্বোধন করেছে, এটি একটি সংকলক বাগের কারণে। এখানে একমাত্র আপাতদৃষ্টিতে অসম্ভবতা 8-বিট জায়গাতে 9-বিট মান, এবং এই আপাত অসম্ভবতাকে স্থানটি রিপোর্ট করা থেকে "বড়" বলে ব্যাখ্যা করা হয়েছে।
স্বাক্ষরিত

আমি এটি কেবলমাত্র আমার মেশিনে পরীক্ষা করেছি এবং ফলাফলটি যা হওয়া উচিত তা হ'ল। সি: -120 সি: -121 সি: -122 সি: -123 সি: -124 সি: -125 সি: -126 সি: -127 সি: -128 সি: 127 সি: 126 সি: 125 সি: 124 সি: 123 সি: 122 সি: 121 সি: 120 সি: 119 সি: 118 সি: 117 এবং আমার পরিবেশটি হ'ল উবুন্টু-12.10
গিসি

উত্তর:


111

এটি একটি সংকলক বাগ।

অপরিজ্ঞাত আচরণের জন্য অসম্ভব ফল পাওয়া বৈধ পরিণতি হলেও, আপনার কোডটিতে আসলে কোনও সংজ্ঞায়িত আচরণ নেই। যা ঘটছে তা হ'ল সংকলকটি আচরণটি অপরিজ্ঞাত বলে মনে করে এবং সেই অনুসারে অনুকূলিত করে।

যদি cহিসাবে সংজ্ঞায়িত করা হয় int8_t, এবং int8_tথেকে প্রচার করে int, তারপর c--বিয়োগ সঞ্চালন অনুমিত হয় c - 1যে intগাণিতিক এবং ফলাফলের পিছনে রূপান্তর int8_t। এর বিয়োগটি intওভারফ্লো হয় না এবং সীমার বাইরে থাকা ইন্টিগ্রাল মানগুলিকে অন্য ইন্টিগ্রাল টাইপে রূপান্তর করা বৈধ। গন্তব্যের ধরণের স্বাক্ষরিত হলে ফলাফলটি বাস্তবায়ন-সংজ্ঞায়িত হয় তবে গন্তব্য প্রকারের জন্য এটি অবশ্যই একটি বৈধ মান হওয়া উচিত। (এবং যদি গন্তব্যের ধরণ স্বাক্ষরিত না হয় তবে ফলাফলটি ভালভাবে সংজ্ঞায়িত করা হয়েছে তবে এটি এখানে প্রযোজ্য নয়))


আমি এটিকে "বাগ" হিসাবে বর্ণনা করব না। যেহেতু স্বাক্ষরিত ওভারফ্লো অনির্ধারিত আচরণের কারণ হয়ে থাকে, সংকলকটি এটি ঘটবে না বলে ধরে নেওয়ার পুরোপুরি অধিকারী, এবং এর মধ্যবর্তী মানকে আরও cবিস্তৃত আকারে রাখার জন্য লুপটি অনুকূলিত করে । সম্ভবত, এখানে কি ঘটছে।
মাইক সিমুর

4
@ মাইকসিমুর: এখানে কেবল ওভারফ্লো (অন্তর্ভুক্ত) রূপান্তর on স্বাক্ষরিত রূপান্তর উপর ওভারফ্লো অনির্ধারিত আচরণ নেই; এটি কেবল একটি বাস্তবায়ন-সংজ্ঞায়িত ফলাফল দেয় (বা বাস্তবায়ন-সংজ্ঞায়িত সংকেত উত্থাপন করে তবে এটি এখানে ঘটছে বলে মনে হয় না)। পাটিগণিত ক্রিয়াকলাপ এবং রূপান্তরগুলির মধ্যে সংজ্ঞা নির্ধারণের পার্থক্য বিজোড়, তবে ভাষা স্ট্যান্ডার্ড এটিরূপে এটি সংজ্ঞা দেয়।
কিথ থমসন

2
@ কিথথম্পসন এটি এমন কিছু যা সি এবং সি ++ এর মধ্যে পৃথক হয়: সি বাস্তবায়ন-সংজ্ঞায়িত সিগন্যালের জন্য অনুমতি দেয়, সি ++ দেয় না। সি ++ ঠিক বলেছে "যদি গন্তব্যের ধরণ স্বাক্ষরিত হয় তবে গন্তব্য প্রকারের (এবং বিট-ফিল্ড প্রস্থ) এ উপস্থাপন করা যেতে পারে যদি মানটি অপরিবর্তিত থাকে; অন্যথায়, মানটি বাস্তবায়ন-সংজ্ঞায়িত হয়।"

এটি যেমন ঘটে যায়, আমি g ++ 4.8.0 এ বিজোড় আচরণটি পুনরুত্পাদন করতে পারি না।
ড্যানিয়েল ল্যান্ডাউ

2
@ ড্যানিয়েল ল্যান্ডাউ সেই বাগে 38 টি মন্তব্য দেখুন: "4.8.0 এর জন্য স্থির করা হয়েছে"। :)

15

একটি সংকলকটিতে এমন বাগ থাকতে পারে যা মানের সাথে নন-কনফর্মেশনগুলি ব্যতীত অন্যান্য প্রয়োজনীয়তা রয়েছে। একটি সংকলক নিজের অন্যান্য সংস্করণের সাথে সামঞ্জস্যপূর্ণ হওয়া উচিত। এটি অন্যান্য সংকলকগুলির সাথে কিছু উপায়ে সুসংগত হতে পারে এবং এর ব্যবহারকারীর বেশিরভাগ বেস দ্বারা পরিচালিত আচরণ সম্পর্কে কিছু বিশ্বাসের সাথে সঙ্গতিপূর্ণ হতে পারে বলেও আশা করা যেতে পারে।

এই ক্ষেত্রে, এটি একটি কনফারেন্স বাগ হিসাবে উপস্থিত হবে। অভিব্যক্তি c--নিপূণভাবে উচিত cএকটি উপায় অনুরূপ c = c - 1। এখানে, cডানদিকে মানটি টাইপ করার জন্য প্রচার করা হয় intএবং তার পরে বিয়োগটি ঘটে। যেহেতু cসীমার মধ্যে রয়েছে তাই int8_tএই বিয়োগটি ওভারফ্লো হবে না, তবে এটি এমন কোনও মান তৈরি করতে পারে যা সীমার বাইরে int8_t। যখন এই মানটি বরাদ্দ করা হয়, তখন রূপান্তরটি আবার টাইপের সাথে সংঘটিত হয় int8_tযাতে ফলাফলটি আবার ফিট করে c। সীমার বাইরে, রূপান্তরটির একটি বাস্তবায়ন-সংজ্ঞায়িত মান থাকে। তবে এর মানের বাইরে থাকা মানটি int8_tকোনও কার্যকর বাস্তবায়ন-সংজ্ঞায়িত মান নয়। একটি বাস্তবায়ন "সংজ্ঞায়িত" করতে পারে না যে একটি 8 বিট ধরণের হঠাৎ 9 বা তত বিট ধারণ করে। মানটি বাস্তবায়ন-সংজ্ঞায়িত হওয়ার অর্থ হ'ল এর পরিসীমাতে কিছু int8_tউত্পাদিত হয় এবং প্রোগ্রামটি অবিরত থাকে। সি স্ট্যান্ডার্ড এর মাধ্যমে স্যাচুরেশন গাণিতিক (ডিএসপির পক্ষে প্রচলিত) বা মোড়কের আশেপাশের (মূলধারার আর্কিটেকচার) মতো আচরণের অনুমতি দেয়।

সংকলক একটি বৃহত্তর অন্তর্নিহিত মেশিন প্রকার ব্যবহার করছে যখন int8_tবা এর মতো ছোট পূর্ণসংখ্যার ধরণের মানগুলি ম্যানিপুলেট করে char। পাটিগণিত সম্পাদন করা হলে, ছোট পূর্ণসংখ্যার প্রকারের বাইরে থাকা ফলাফলগুলি এই বৃহত্তর প্রকারের মধ্যে নির্ভরযোগ্যভাবে ক্যাপচার করা যায়। বহিরাগতভাবে দৃশ্যমান আচরণটি সংরক্ষণ করতে যে ভেরিয়েবলটি একটি 8 বিট প্রকারের, আরও বিস্তৃত ফলাফলটি 8 বিট পরিসরে বিচ্ছিন্ন করতে হবে। সুস্পষ্ট কোডটি করা দরকার যেহেতু মেশিন স্টোরেজ অবস্থানগুলি (নিবন্ধগুলি) 8 বিটের চেয়ে বিস্তৃত এবং বৃহত্তর মানগুলিতে খুশি। এখানে, সংকলকটি মানটিকে স্বাভাবিক করতে অবহেলা করে এবং কেবল এটি printfহিসাবে চলে যায়। রূপান্তর specifier %iমধ্যে printfকোন ধারণা যুক্তি মূলত থেকে এসেছিলেন হয়েছে int8_tগণনার; এটি শুধু একটি সঙ্গে কাজ করছেint যুক্তি.


এটি একটি সুস্পষ্ট ব্যাখ্যা।
ডেভিড হ্যালি

সংকলকটি অপ্টিমাইজারটি বন্ধ করে দিয়ে ভাল কোড তৈরি করে। সুতরাং, "বিধি" এবং "সংজ্ঞা" ব্যবহার করে ব্যাখ্যা প্রযোজ্য নয়। এটি অপটিমাইজারে একটি বাগ।

14

আমি এটি একটি মন্তব্যে ফিট করতে পারি না, তাই আমি এটি উত্তর হিসাবে পোস্ট করছি।

কিছু খুব অদ্ভুত কারণে --অপারেটরটি অপরাধী হিসাবে ঘটে।

আমি আইডিয়নে পোস্ট করা কোডটি পরীক্ষা করেছি এবং এর সাথে প্রতিস্থাপন c--করেছি c = c - 1এবং মানগুলি সীমার মধ্যেই রয়ে গেছে [-128 ... 127]:

c: -123
c: -124
c: -125
c: -126
c: -127
c: -128 // about to overflow
c: 127  // woop
c: 126
c: 125
c: 124
c: 123
c: 122

অদ্ভুত আই? সংকলকটি মত প্রকাশ করতে i++বা পছন্দ করে সে সম্পর্কে আমি বেশি কিছু জানি না i--। এটি সম্ভবত রিটার্ন মানটিকে প্রচার করে intএবং এটি পাস করে। এটিই কেবলমাত্র যৌক্তিক উপসংহারের সাথে আমি আসতে পারি কারণ আপনি আসলে এমন মান পাচ্ছেন যা 8-বিটের মধ্যে মাপসই করা যায় না।


4
অবিচ্ছেদ্য প্রচারের কারণে, c = c - 1মানে c = (int8_t) ((int)c - 1। একটি সীমার বাইরে রূপান্তরিত আচরণের সংজ্ঞা intদেওয়া int8_tহয়েছে তবে একটি বাস্তবায়ন-সংজ্ঞায়িত ফলাফল। আসলে, c--একই রূপান্তরগুলিও খুব বেশি সম্পাদন করার কথা নয়?

12

আমি অনুমান করি যে অন্তর্নিহিত হার্ডওয়্যার এখনও সেই অন্তর্নির্মিত আটটি ধরে রাখতে 32-বিট রেজিস্টার ব্যবহার করছে। যেহেতু স্পেসিফিকেশন ওভারফ্লোতে কোনও আচরণ চাপায় না তাই বাস্তবায়ন ওভারফ্লো পরীক্ষা করে না এবং বৃহত্তর মানগুলিকেও সংরক্ষণ করতে দেয়।


আপনি যদি স্থানীয় ভেরিয়েবলটিকে চিহ্নিত করেন volatileআপনি এটির জন্য মেমরি ব্যবহার করতে বাধ্য করছেন এবং ফলস্বরূপ পরিসরের মধ্যে প্রত্যাশিত মানগুলি অর্জন করতে পারেন।


1
কি শান্তি. আমি ভুলে গিয়েছিলাম যে সংকলিত সমাবেশটি যদি সম্ভব হয় তবে স্থানীয় ভেরিয়েবলগুলি রেজিস্টারে সংরক্ষণ করবে। এটি সম্ভবত ফর্ম্যাট মানগুলির printfযত্ন না নেওয়ার পাশাপাশি উত্তর বলে মনে হচ্ছে sizeof
rliu

3
@ রোলিও জি ++ -O2-এস কোড.cpp চালান, এবং আপনি সমাবেশটি দেখতে পাবেন। তদুপরি, প্রিন্টফ () একটি পরিবর্তনশীল আর্গুমেন্ট ফাংশন, সুতরাং যার আর্গুমেন্টগুলির একটি ইন্টের চেয়ে কম হয় তা কোন ইনট হিসাবে প্রচার করা হবে।
Nos

@ নোস আমি চাই আমার মেশিনে আর্চলিনাক্স চালানোর জন্য আমি কোনও ইউইএফআই বুট লোডার (বিশেষত আরইএফআইআইডি) ইনস্টল করতে সক্ষম হয়েছি না, তাই আমি আসলে দীর্ঘকাল জিএনইউ সরঞ্জাম দিয়ে কোডড করি নি। আমি এটা ... শেষ পর্যন্ত পেতে হবে। আপাতত এটি ভিএস-তে কেবল সি # এবং
সিটিকে

একটি ভার্চুয়াল মেশিনে @rollu চালান এটা যেমন VirtualBox
আমরা

@ নোস টপিক লেনদেন করতে চান না, তবে হ্যাঁ, আমি পারলাম। আমি কেবল একটি BIOS বুটলোডার সহ লিনাক্স ইনস্টল করতে পারি। আমি কেবল একগুঁয়ে এবং আমি যদি এটি কোনও ইউইএফআইয়ের বুটলোডারের সাথে কাজ করতে না পারি তবে আমি সম্ভবত এটি এটিকে মোটেও কাজ করব না: পি।
rliu

11

এসেম্বলারের কোডটি সমস্যাটি প্রকাশ করে:

:loop
mov esi, ebx
xor eax, eax
mov edi, OFFSET FLAT:.LC2   ;"c: %i\n"
sub ebx, 1
call    printf
cmp ebx, -301
jne loop

mov esi, -45
mov edi, OFFSET FLAT:.LC2   ;"c: %i\n"
xor eax, eax
call    printf

ইবিএক্স এফএফ পোস্ট হ্রাসের সাথে যুক্ত হতে হবে, বা কেবল বিএল ইবিএক্সের অবশিষ্টাংশের সাথে ব্যবহার করা উচিত। কৌতূহল যে এটি ডিসের পরিবর্তে সাব ব্যবহার করে। -45 ফ্ল্যাট-আউট রহস্যজনক। এটি 300 এবং 255 = 44. -45 = ~ 44 এর বিটওয়াইজ বিপরীত। কোথাও একটি সংযোগ আছে।

এটি c = c - 1 ব্যবহার করে আরও অনেক কাজ করে যায়:

mov eax, ebx
mov edi, OFFSET FLAT:.LC2   ;"c: %i\n"
add ebx, 1
not eax
movsx   ebp, al                 ;uses only the lower 8 bits
xor eax, eax
mov esi, ebp

এরপরে এটি কেবল RAX এর নীচের অংশটি ব্যবহার করে, তাই এটি 127 থেকে -128 এর মধ্যে সীমাবদ্ধ।

কোনও অপ্টিমাইজেশন ছাড়াই, এটি সঠিক কোড তৈরি করে:

movzx   eax, BYTE PTR [rbp-1]
sub eax, 1
mov BYTE PTR [rbp-1], al
movsx   edx, BYTE PTR [rbp-1]
mov eax, OFFSET FLAT:.LC2   ;"c: %i\n"
mov esi, edx

সুতরাং এটি অপ্টিমাইজারে একটি বাগ।


4

%hhdপরিবর্তে ব্যবহার করুন %i! আপনার সমস্যা সমাধান করা উচিত।

আপনি যা দেখতে পাচ্ছেন তার সাথে কম্পাইলার অপ্টিমাইজেশনের ফলাফলটি প্রিন্টফকে 32 বিবিট নম্বর মুদ্রণ করতে বলেছে এবং তারপরে একটি (অনুমিত 8 বিট) নম্বরটি স্ট্যাকের উপরে ঠেলে দিচ্ছে, যা সত্যই পয়েন্টার আকারের, কারণ x86 এ পুশ অপকোড এভাবে কাজ করে।


1
আমি ব্যবহার করে আমার সিস্টেমে আসল আচরণটি পুনরুত্পাদন করতে সক্ষম g++ -O3। পরিবর্তন %iকরার জন্য %hhdকিছু পরিবর্তন করবেন না।
কিথ থম্পসন

3

আমি মনে করি কোডটির অপ্টিমাইজেশন করে এটি করা হচ্ছে:

for (int32_t i = 0; i <= 300; i++)
      printf("c: %i\n", c--);

int32_t iসংকলক উভয় জন্য iএবং ভেরিয়েবল ব্যবহার c। অপ্টিমাইজেশন বন্ধ করুন বা সরাসরি castালাই করুন printf("c: %i\n", (int8_t)c--);


তারপরে অপ্টিমাইজেশন বন্ধ করুন। বা এর মতো কিছু করুন:(int8_t)(c & 0x0000ffff)--
ভেসিভলড

1

cহয় নিজেই হিসাবে সংজ্ঞায়িত করা int8_t, কিন্তু অপারেটিং ++বা --ওভার int8_tএটা পরোক্ষভাবে প্রথম রূপান্তরিত হয় intএবং অপারেশন ফলাফলের পরিবর্তে গ অভ্যন্তরীণ মান printf, যা হতে হবে সঙ্গে ছাপা হয় int

দেখুন প্রকৃত মূল্য এর c, সমগ্র লুপ পর বিশেষত গত হ্রাস পর

-301 + 256 = -45 (since it revolved entire 8 bit range once)

এটি সঠিক মান যা আচরণের সাথে সাদৃশ্যপূর্ণ -128 + 1 = 127

cintআকারের মেমরি ব্যবহার করা শুরু করে তবে int8_tযখন কেবল নিজের ব্যবহার করে মুদ্রিত হয় তখন তা মুদ্রিত হয় 8 bits32 bitsহিসাবে ব্যবহৃত হয় সমস্ত ব্যবহার করেint

[সংকলক বাগ]


0

আমি মনে করি এটি ঘটেছে কারণ আপনার লুপটি ইনট্রি 300 না হওয়া পর্যন্ত এবং গ -300 হয়ে যাওয়া অবধি চলে। এবং শেষ মান কারণ

printf("c: %i\n", c);

'সি' একটি 8 বিটের মান, সুতরাং এটি -300 হিসাবে বৃহত্তর সংখ্যাটি ধরে রাখা অসম্ভব।
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.