ছদ্মবেশী সি কোডের এই চারটি লাইনের পিছনে ধারণা


384

কেন এই কোড আউটপুট দেয় C++Sucks? এর পেছনে ধারণাটি কী?

#include <stdio.h>

double m[] = {7709179928849219.0, 771};

int main() {
    m[1]--?m[0]*=2,main():printf((char*)m);    
}

এটি এখানে পরীক্ষা করুন


1
@ বুবটি ফিশ প্রযুক্তিগতভাবে, হ্যাঁ, তবে এটি সি -৯৯
তে

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

6
@ ডিটোনেটর 123 "আপনার অবশ্যই এখানে নতুন হওয়া উচিত" - আপনি যদি ক্লোজারের কারণটি দেখেন তবে আপনি খুঁজে পেতে পারেন যে এটি তেমন নয়। প্রয়োজনীয় ন্যূনতম বোধগম্যতা আপনার প্রশ্ন থেকে স্পষ্টভাবে অনুপস্থিত - "আমি এটি বুঝতে পারি না, এটি ব্যাখ্যা করুন" স্ট্যাক ওভারফ্লোতে স্বাগত জানানো এমন কিছু নয়। আপনি যদি প্রথমে নিজেকে কিছু চেষ্টা করে দেখেন , তাহলে প্রশ্নটি বন্ধ না হত। এটি গুগল "ডাবল উপস্থাপনা সি" বা এর মতো অপ্রয়োজনীয়।

42
আমার বিগ-এন্ডিয়ান পাওয়ারপিসি মেশিন প্রিন্ট করে skcuS++C
অ্যাডাম রোজেনফিল্ড

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

উত্তর:


494

নম্বরটিতে 7709179928849219.0একটি 64-বিট হিসাবে নিম্নলিখিত বাইনারি উপস্থাপনা রয়েছে double:

01000011 00111011 01100011 01110101 01010011 00101011 00101011 01000011
+^^^^^^^ ^^^^---- -------- -------- -------- -------- -------- --------

+চিহ্নটির অবস্থান প্রদর্শন করে; ^ঘাতক এবং- অংশক এর (এক্সপোনেন্ট ছাড়া মান অর্থাৎ)।

যেহেতু উপস্থাপনাটি বাইনারি এক্সপোনেন্ট এবং ম্যান্টিসা ব্যবহার করে, সংখ্যাকে দ্বিগুণ করে একের সাথে এক্সপোনেন্টকে বৃদ্ধি করে। আপনার প্রোগ্রামটি যথাযথভাবে এটি 771 বার করে, তাই 1075-এ শুরু হওয়া সূচকটি (এর দশমিক প্রতিনিধিত্ব 10000110011) শেষে 1075 + 771 = 1846 হয়ে যায়; 1846 এর বাইনারি উপস্থাপনা হয় 11100110110। ফলাফলের ধরণটি এরকম দেখাচ্ছে:

01110011 01101011 01100011 01110101 01010011 00101011 00101011 01000011
-------- -------- -------- -------- -------- -------- -------- --------
0x73 's' 0x6B 'k' 0x63 'c' 0x75 'u' 0x53 'S' 0x2B '+' 0x2B '+' 0x43 'C'

এই প্যাটার্নটি আপনি যে স্ট্রিংটি প্রিন্ট করেছেন কেবল তার পিছনেই দেখায় to একই সময়ে, অ্যারের দ্বিতীয় উপাদানটি শূন্য হয়ে যায়, নাল টার্মিনেটর সরবরাহ করে, স্ট্রিংটি পাসের জন্য উপযুক্ত করে তোলে printf()


22
স্ট্রিং পিছন দিকে কেন?
ডেরেক

95
@ ডেরেক x86 ছোট্ট এন্ডিয়ান
অ্যাঞ্জিউ আর

16
@Derek এই প্ল্যাটফর্ম-নির্দিষ্ট কারণ endianness : বিমূর্ত আইইইই 754 প্রতিনিধিত্বের বাইট তাই স্ট্রিং কপি করে প্রিন্ট সঠিকভাবে কমছে ঠিকানাগুলি এ মেমরি সংরক্ষণ করা হয়। বড় প্রান্তিকতা সহ হার্ডওয়্যারটিতে একটি আলাদা সংখ্যা দিয়ে শুরু করা দরকার।
ডাসব্লিংকনলাইট

14
@ অ্যালভিনওয়ং আপনি সঠিক, মানকটির জন্য আইইইই 754 বা অন্য কোনও নির্দিষ্ট বিন্যাসের প্রয়োজন হয় না। এই প্রোগ্রামটি যতটা নন পোর্টেবল পায় ততই কাছাকাছি বা এর খুব কাছে :-)
ডাসব্লিংকনলাইট

10
@ গ্রিজেশচৌহান আমি আইইইই 7575৫ ক্যালকুলেটর ডাবল-স্পষ্টতা ব্যবহার করেছি : আমি 7709179928849219মানটি পেস্ট করেছি এবং বাইনারি উপস্থাপনা ফিরে পেয়েছি।
dasblinkenlight

223

আরও পঠনযোগ্য সংস্করণ:

double m[2] = {7709179928849219.0, 771};
// m[0] = 7709179928849219.0;
// m[1] = 771;    

int main()
{
    if (m[1]-- != 0)
    {
        m[0] *= 2;
        main();
    }
    else
    {
        printf((char*) m);
    }
}

এটি পুনরাবৃত্তি কল করে main() 771 বার ।

শুরুতে, m[0] = 7709179928849219.0যা দাঁড়িয়েছে জন্য C++Suc;C। প্রতিটি কলে, m[0]দ্বিগুণ হয়ে যায়, শেষ দুটি অক্ষর "মেরামত" করতে। শেষ কলটিতে m[0]ASCII চর প্রতিনিধিত্ব করে C++Sucksএবং m[1]এতে কেবল জিরো থাকে, সুতরাং এতে স্ট্রিংয়ের জন্য নাল টার্মিনেটর রয়েছেC++Sucks । সব অনুমানের অধীনেm[0] 8 টি বাইটে সঞ্চিত হয়, সুতরাং প্রতিটি চরটি 1 বাইট নেয়।

পুনরাবৃত্তি এবং অবৈধ main()কলিং ছাড়া এটি এর মতো দেখতে পাবেন:

double m[] = {7709179928849219.0, 0};
for (int i = 0; i < 771; i++)
{
    m[0] *= 2;
}
printf((char*) m);

8
এটি পোস্টফিক্স হ্রাস। সুতরাং এটি 771 বার বলা হবে।
জ্যাক এইডলি 1'13

106

দাবি অস্বীকার : এই উত্তরটি প্রশ্নের মূল ফর্মটিতে পোস্ট করা হয়েছিল, এতে কেবল সি ++ উল্লেখ করা হয়েছিল এবং এতে একটি সি ++ শিরোনাম অন্তর্ভুক্ত ছিল। প্রশ্নের খাঁটি সিতে রূপান্তরটি মূল প্রশ্নকর্তার ইনপুট ছাড়াই সম্প্রদায় করেছিল।


সাধারণভাবে বলতে গেলে, এই প্রোগ্রামটি সম্পর্কে তর্ক করা অসম্ভব কারণ এটি অসম্পূর্ণ (যেমন এটি আইনী সি ++ নয়)। এটি সি ++ 11 [বেসিক.স্টার্ট.মাইন] পি 3 লঙ্ঘন করে:

ফাংশন প্রধান কোনও প্রোগ্রামের মধ্যে ব্যবহার করা হবে না।

এটি অন্যদিকে, এটি নির্ভর করে যে একটি সাধারণ গ্রাহক কম্পিউটারে, একটি double8 বাইট দীর্ঘ হয় এবং একটি নির্দিষ্ট সুপরিচিত অভ্যন্তরীণ উপস্থাপনা ব্যবহার করে। অ্যারের প্রাথমিক মানগুলি গণনা করা হয় যাতে "অ্যালগরিদম" সম্পাদন করা হলে, প্রথমটির চূড়ান্ত মানটি doubleএমন হবে যে অভ্যন্তরীণ উপস্থাপনা (8 বাইট) 8 টি অক্ষরের ASCII কোড হবে C++Sucks। অ্যারেতে দ্বিতীয় উপাদানটি তখন 0.0, যার প্রথম বাইটটি 0অভ্যন্তরীণ উপস্থাপনায় থাকে এটি একে বৈধ সি-স্টাইলের স্ট্রিং তৈরি করে। এরপরে এটি ব্যবহার করে আউটপুট প্রেরণ করা হয় printf()

এইচডাব্লুতে এটি চালানোর ফলে উপরের কিছু আবদ্ধ না করে এর পরিবর্তে আবর্জনা পাঠ্য হবে (বা সম্ভবত সীমার বাইরেও অ্যাক্সেস)।


25
আমি যোগ করতে হবে যে এটি সি ++ 11 এর কোনও আবিষ্কার নয় - সি ++ 03 basic.start.mainএর একই শব্দযুক্ত সাথে 3.6.1 / 3 ছিল had
শর্টটোথ

1
এই ছোট উদাহরণটির মূল বিষয় হল সি ++ দিয়ে কী করা যায় তা চিত্রিত করা। ইউবি ট্রিকস বা "ক্লাসিক" কোডের বিশাল সফ্টওয়্যার প্যাকেজ ব্যবহার করে ম্যাজিক নমুনা।
শেচপুরিন

1
@ শরফতূথ এটি যোগ করার জন্য ধন্যবাদ। আমি অন্যথায় বোঝানো বলতে চাইনি, আমি কেবল আমার ব্যবহৃত মানটি উদ্ধৃত করেছি।
অ্যাঞ্জিউ আর

@ অ্যাঙ্গিউ: ইয়াপ, আমি বুঝতে পেরেছি, কেবল এটি বলতে চেয়েছিলেন যে শব্দটি বেশ পুরানো।
শার্পথুথ

1
@ জিমবাল্টার নোটিশ আমি বলেছি "আনুষ্ঠানিকভাবে বলতে গেলে, যুক্তি দেওয়া অসম্ভব," না "আনুষ্ঠানিকভাবে যুক্তি দেওয়া অসম্ভব।" আপনি ঠিক বলেছেন যে প্রোগ্রামটি সম্পর্কে तर्क করা সম্ভব, তবে এটি করতে ব্যবহৃত সংকলকটির বিশদ আপনার জানা দরকার। কেবলমাত্র কলটি মুছে ফেলা বা হার্ডড্রাইভ বা অন্য যে কোনও কিছুতে ফর্ম্যাট করার জন্য এপিআই কল দিয়ে এটি প্রতিস্থাপনের জন্য এটি সম্পূর্ণরূপে সংকলকের অধিকারগুলির মধ্যে থাকবে main()
অ্যাঞ্জিউ আর

57

সম্ভবত কোডটি বোঝার সবচেয়ে সহজ উপায় হ'ল বিপরীতে থাকা জিনিসগুলির মাধ্যমে কাজ করা। আমরা মুদ্রণের জন্য একটি স্ট্রিং দিয়ে শুরু করব - ভারসাম্যের জন্য, আমরা "সি ++ রকস" ব্যবহার করব। গুরুতর বিষয়: ঠিক আসলটির মতোই, এটি ঠিক আটটি অক্ষর দীর্ঘ long যেহেতু আমরা আসলটির মতো (মোটামুটি) করতে যাচ্ছি এবং এটি বিপরীতক্রমে মুদ্রণ করব, তাই আমরা এটিকে বিপরীত ক্রমে রেখে শুরু করব। আমাদের প্রথম পদক্ষেপের জন্য, আমরা কেবল সেই বিট প্যাটার্নটিকে একটি হিসাবে দেখব doubleএবং ফলাফলটি মুদ্রণ করব:

#include <stdio.h>

char string[] = "skcoR++C";

int main(){
    printf("%f\n", *(double*)string);
}

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

#include <stdio.h>

double x [] = { 978874550692723072, 8 };
char *y = (char *)x;

int main(int argc, char **argv){
    if (x[1]) {
        x[0] /= 2;  
        main(--x[1], (char **)++y);
    }
    putchar(*--y);
}

এখন আমাদের কাছে প্রচুর ingালাই রয়েছে, (পুনরাবৃত্ত) main সম্পূর্ণভাবে উপেক্ষা করা হয়েছে (তবে মূল্যবৃদ্ধি এবং হ্রাস পেতে মূল্যায়ন একেবারে গুরুত্বপূর্ণ), এবং অবশ্যই আমরা কী করছি এই বিষয়টি আড়াল করার জন্য সম্পূর্ণ স্বেচ্ছাচারিত সংখ্যার সংখ্যা সত্যিই বেশ সোজা।

অবশ্যই, পুরো পয়েন্টটি যেহেতু অস্পষ্ট, তাই যদি আমাদের মনে হয় আমরা আরও পদক্ষেপ নিতে পারি। যেমন উদাহরণস্বরূপ, আমরা আমাদের ifবিবৃতিটিকে একক অভিব্যক্তিতে পরিণত করতে শর্ট সার্কিট মূল্যায়নের সুবিধা নিতে পারি , তাই মূল চেহারাটি এইরকম দেখাচ্ছে:

x[1] && (x[0] /= 2,  main(--x[1], (char **)++y));
putchar(*--y);

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

আমাদের পরবর্তী পদক্ষেপটি সম্ভবত প্রতিটি অক্ষর মুদ্রণটিকে সেই চরিত্রটি সন্ধান থেকে পৃথক করা। আমরা যথাযথ সহজেই সঠিক অক্ষরটি যেখান থেকে ফেরতের মান হিসাবে তৈরি করতে পারি mainএবং কী mainফিরে আসে তা মুদ্রণ করে এটি করতে পারি :

x[1] && (x[0] /= 2,  putchar(main(--x[1], (char **)++y)));
return *--y;

কমপক্ষে আমার কাছে, এটি যথেষ্ট আপত্তিজনক বলে মনে হচ্ছে, তাই আমি এটি এ ছেড়ে দেব।


1
ফরেনসিক পদ্ধতির পছন্দ।
রাইকার

24

এটি কেবল একটি ডাবল অ্যারে (16 বাইট) তৈরি করছে যা - যদি চর অ্যারে হিসাবে ব্যাখ্যা করা হয় - "সি ++ সাকস" স্ট্রিংয়ের জন্য ASCII কোডগুলি তৈরি করুন

তবে কোডটি প্রতিটি সিস্টেমে কাজ করছে না, এটি নিম্নলিখিত কয়েকটি অপরিজ্ঞাত তথ্যের উপর নির্ভর করে:

  • ডাবল ঠিক 8 বাইট আছে
  • endianness


11

অন্যরা প্রশ্নটি পুরোপুরিভাবে ব্যাখ্যা করেছে, আমি একটি নোট যুক্ত করতে চাই যে এটি স্ট্যান্ডার্ড অনুযায়ী নির্ধারিত আচরণ

সি ++ 11 3.6.1 / 3 প্রধান ফাংশন

ফাংশন প্রধান কোনও প্রোগ্রামের মধ্যে ব্যবহার করা হবে না। মূলটির লিঙ্কেজ (3.5) বাস্তবায়ন-সংজ্ঞায়িত। এমন একটি প্রোগ্রাম যা মুখ্যটিকে মুছে ফেলা হিসাবে সংজ্ঞায়িত করে বা প্রধানকে ইনলাইন, স্ট্যাটিক বা কনস্টেক্সপ্র হিসাবে ঘোষিত হয় যা দুর্গঠিত। নামটির নামটি অন্যথায় সংরক্ষিত নয়। [উদাহরণস্বরূপ: সদস্য ফাংশন, শ্রেণি এবং গণনাগুলিকে প্রধান বলা যেতে পারে, অন্য নামের জায়গাগুলিতে সত্তাও। পরবর্তী উদাহরণ]


1
আমি বলব এটি এমনকি খারাপ-গঠিত (যেমনটি আমি আমার জবাব দিয়েছি) - এটি একটি "উইল" লঙ্ঘন করে।
অ্যাঞ্জিউ আর

9

কোডটি আবার এভাবে লেখা যেতে পারে:

void f()
{
    if (m[1]-- != 0)
    {
        m[0] *= 2;
        f();
    } else {
          printf((char*)m);
    }
}

এটি যা করছে তা বাইটের একটি সেট তৈরি করছে double অ্যারেতেm 'সি ++ সাকস' অক্ষরের সাথে মিলিত হয়ে নাল-টার্মিনেটর দ্বারা অনুসরণ করা হয়। তারা কোডটি দ্বিগুণ করেছেন যা ডাবল মান বাছাই করে যা দ্বিগুণ হয়ে গেলে 77 77১ বার উত্পন্ন হয়, স্ট্যান্ডার্ড উপস্থাপনায়, অ্যারের দ্বিতীয় সদস্য দ্বারা সরবরাহিত নাল টার্মিনেটরের সাথে বাইট সেট করে।

মনে রাখবেন যে এই কোডটি ভিন্ন এন্ডিয়ান উপস্থাপনার অধীনে কাজ করবে না। এছাড়াও, কল করার main()কঠোরভাবে অনুমতি নেই।


3
আপনার fরিটার্ন কেন আসে int?
বাম দিকের বাইরে

1
হ্যাঁ, আমি intপ্রশ্নটিতে রিটার্নটি অনুলিপি করছিলাম । আমাকে এটি ঠিক করতে দিন।
জ্যাক এইডলি 15

1

প্রথমে আমাদের স্মরণ করা উচিত যে ডাবল যথার্থ সংখ্যাগুলি মেমরিতে বাইনারি ফর্ম্যাটে নিম্নরূপে সংরক্ষণ করা হয়:

(i) সাইন ইন করার জন্য 1 বিট

(ii) ঘাতকের জন্য 11 বিট

(iii) परिमाणের জন্য 52 বিট

বিটের ক্রমটি (i) থেকে কমে যায় (iii)।

প্রথমে দশমিক ভগ্নাংশ সংখ্যা সমান ভগ্নাংশ বাইনারি সংখ্যায় রূপান্তরিত হয় এবং তারপরে এটি বাইনারি আকারের আকারের ক্রম হিসাবে প্রকাশ করা হয়।

সুতরাং সংখ্যাটি 7709179928849219.0 হয়

(11011011000110111010101010011001010110010101101000011)base 2


=1.1011011000110111010101010011001010110010101101000011 * 2^52

দৈর্ঘ্যের বিট বিবেচনা করার সময়, পরিমাপ পদ্ধতির সমস্ত ক্রম শুরু হওয়ার সাথে সাথে এটি অবহেলিত 1 ।

সুতরাং প্রস্থের অংশটি হয়ে যায়:

1011011000110111010101010011001010110010101101000011 

এখন ক্ষমতা 2 হয় 52 , আমরা যেমন নম্বর biasing যোগ করতে হবে 2 ^ (এক্সপোনেন্ট জন্য বিট -1) -1 অর্থাত 2 ^ (11 -1) -1 = 1023 , তাই আমাদের সূচক হয়ে 52 + + 1023 = 1075

এখন আমাদের কোড mutiplies সঙ্গে সংখ্যা 2 , 771 বার যার দ্বারা বৃদ্ধি এক্সপোনেন্ট তোলে 771

সুতরাং আমাদের প্রকাশক (1075 + 771) = 1846 যার বাইনারি সমতুল্য (11100110110)

এখন আমাদের সংখ্যাটি ইতিবাচক তাই আমাদের সাইন বিট 0 হয়

সুতরাং আমাদের পরিবর্তিত নম্বর হয়ে যায়:

সাইন বিট + এক্সপোনেন্ট + প্রস্থ (বিটগুলির সরল সংমিশ্রণ)

0111001101101011011000110111010101010011001010110010101101000011 

যেহেতু মিটার চর পয়েন্টারে রূপান্তরিত হয়েছে আমরা এলএসডি থেকে বিট প্যাটার্নটিকে 8 এর অংশে বিভক্ত করব

01110011 01101011 01100011 01110101 01010011 00101011 00101011 01000011 

(যার হেক্স সমতুল্য :)

 0x73 0x6B 0x63 0x75 0x53 0x2B 0x2B 0x43 

আসকি চার্ট চরিত্রের মানচিত্র থেকে কোনটি প্রদর্শিত হয়েছে:

s   k   c   u      S      +   +   C 

এখন একবার এটি এম করা হয়ে গেলে [1] 0 হয় যার অর্থ একটি শূন্য চরিত্র

এখন ধরে নিচ্ছেন যে আপনি এই প্রোগ্রামটি একটি সামান্য এন্ডিয়ান মেশিনে চালাচ্ছেন (লোয়ার অর্ডার বিটটি নীচের ঠিকানায় সংরক্ষণ করা হয়) সুতরাং পয়েন্টার এম পয়েন্টারটি সর্বনিম্ন ঠিকানার বিটে এবং তারপরে 8 এর চকে বিট নিয়ে এগিয়ে যায় (টাইপ হিসাবে চরকে টানা * ) এবং প্রিন্টফ () শেষ খণ্ডে যখন 00000000 এর মুখোমুখি হয় ...

এই কোডটি বহনযোগ্য নয়।

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