c++ - কেন এই ফাংশন কল একটি টাইপcast ফাংশন পয়েন্টার মাধ্যমে এটি কল করার পর sensibly আচরণ করে?




gcc function-pointers (3)

অন্যদের হিসাবে নির্দিষ্ট করা হয়েছে, এই অনির্ধারিত আচরণ, তাই নীতিগতভাবে কি ঘটতে পারে সব বিট বন্ধ হয়। কিন্তু আপনি একটি x86 মেশিনে আছেন বলে মনে করছেন, কেন আপনি এটি দেখতে পাচ্ছেন তার একটি সম্ভাব্য ব্যাখ্যা রয়েছে।

X86 এ, g ++ কম্পাইলার সবসময় স্ট্যাক সম্মুখের দিকে ধাক্কা দিয়ে আর্গুমেন্টগুলি পাস করে না। পরিবর্তে, এটি নিবন্ধকদের মধ্যে প্রথম কয়েকটি আর্গুমেন্ট stashes। যদি আমরা f ফাংশনটি বিচ্ছিন্ন করে দেখি, প্রথম কয়েকটি নির্দেশিকা নিবন্ধকের বাইরে আর্গুমেন্টগুলি সরাতে এবং স্পষ্টভাবে স্ট্যাকের দিকে চলে যায়:

    push    rbp
    mov     rbp, rsp
    sub     rsp, 16
    mov     DWORD PTR [rbp-4], edi  # <--- Here
    mov     DWORD PTR [rbp-8], esi  # <--- Here
    # (many lines skipped)

একইভাবে, কল কিভাবে main উত্পাদিত হয় তা লক্ষ্য করুন। আর্গুমেন্ট ঐ নিবন্ধকদের মধ্যে স্থাপন করা হয়:

    mov     rax, QWORD PTR [rbp-8]
    mov     edx, 30      # <--- Here
    mov     esi, 20      # <--- Here
    mov     edi, 10      # <--- Here
    call    rax

যেহেতু পুরো নিবন্ধটি আর্গুমেন্টগুলি ধরে রাখার জন্য ব্যবহার করা হচ্ছে, তাই আর্গুমেন্টগুলির আকার এখানে প্রাসঙ্গিক নয়।

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

আমি নিম্নলিখিত কোড আছে। দুটি int32 লাগে যে একটি ফাংশন আছে। তারপর আমি একটি পয়েন্টার নিতে এবং একটি ফাংশন এটি নিক্ষেপ যে তিন int8 লাগে এবং এটি কল। আমি একটি রানটাইম ত্রুটি প্রত্যাশিত কিন্তু প্রোগ্রাম জরিমানা কাজ করে। কেন এটা সম্ভব?

main.cpp:

#include <iostream>

using namespace std;

void f(int32_t a, int32_t b) {
    cout << a << " " << b << endl;
}

int main() {
    cout << typeid(&f).name() << endl;
    auto g = reinterpret_cast<void(*)(int8_t, int8_t, int8_t)>(&f);
    cout << typeid(g).name() << endl;
    g(10, 20, 30);
    return 0;
}

আউটপুট:

PFviiE
PFvaaaE
10 20

আমি দেখতে পাচ্ছি প্রথম ফাংশনের স্বাক্ষর দুটি ইন্টের প্রয়োজন এবং দ্বিতীয় ফাংশনটির জন্য তিনটি অক্ষর প্রয়োজন। চারটি int থেকে ছোট এবং আমি বিস্মিত কেন a এবং b এখনও 10 এবং ২0 এর সমান।


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

আমি গডবোল্ট ব্যবহার করে সমাবেশটি তৈরি করার জন্য, here আপনি সম্পূর্ণ চেক করতে পারেন

প্রাসঙ্গিক ফাংশন কল এখানে:

mov     edi, 10
mov     esi, 20
mov     edx, 30
call    f(int, int) #clang totally knows you're calling f by the way

এটি স্ট্যাকের প্যারামিটারগুলিকে ধাক্কা দেয় না, এটি কেবল তাদের নিবন্ধকগুলিতে রাখে। সবচেয়ে আকর্ষণীয় বিষয় হল যে mov নির্দেশটি নিবন্ধটির নিম্ন 8 বিট পরিবর্তন করে না, তবে এটির সবগুলি 32-বিট পদক্ষেপ হিসাবে থাকে। এটির অর্থ হল যে নিবন্ধটির আগে কোনও ব্যাপার ছিল না, আপনি যখন 32 বিট ফেরত পাঠবেন তখন আপনি সর্বদা সঠিক মান পাবেন।

যদি আপনি 32-বিট পদক্ষেপটি কেন ভাবছেন, এটি প্রায় প্রতিটি ক্ষেত্রে, x86 বা AMD64 আর্কিটেকচারে, কম্পাইলারগুলি সর্বদা 32 বিট আক্ষরিক প্যাচ বা 64 বিট আক্ষরিক প্যাচসমূহ ব্যবহার করবে (যদি এবং মানটি খুব বড় হলে শুধুমাত্র 32 বিট জন্য)। একটি 8 বিট মান মুভিং নিবন্ধনের উপরের বিটগুলি (8-31) শূন্য নয়, এবং মানটি প্রচার করা শেষ হয়ে গেলে সমস্যাগুলি তৈরি করতে পারে। একটি 32-বিট আক্ষরিক নির্দেশনা ব্যবহার করে নিবন্ধনটি প্রথম শূন্য করতে অতিরিক্ত নির্দেশনা দেওয়ার চেয়ে আরও সহজ।

যদিও আপনাকে মনে রাখতে হবে এক জিনিস এটি আসলেই ফাকে কল করার চেষ্টা করছে যেমন এটি 8 বিট পরামিতি ছিল, তাই যদি আপনি একটি বড় মান রাখেন তবে এটি আক্ষরিক ছাঁটাই হয়ে যাবে। উদাহরণস্বরূপ, 1000 -24 হ'ল -24 , নিচের বিটগুলি E8 , যা -24 যখন স্বাক্ষরিত পূর্ণসংখ্যা ব্যবহার করে। আপনি একটি সতর্কতা পাবেন

<source>:13:7: warning: implicit conversion from 'int' to 'signed char' changes value from 1000 to -24 [-Wconstant-conversion]

প্রথম সি কম্পাইলার এবং সি-র স্ট্যান্ডার্ড প্রকাশের পূর্বে যেগুলি সবচেয়ে বেশি কম্পাইলার ডান-থেকে-বামে আর্গুমেন্টগুলি চাপিয়ে একটি ফাংশন কল প্রক্রিয়া করবে, ফাংশনটি আহ্বান করার জন্য প্ল্যাটফর্মের "কল সাবরুটিন" নির্দেশনাটি ব্যবহার করবে এবং তারপরে তারপর ফাংশন ফিরে পরে, যাই হোক না কেন আর্গুমেন্ট ধাক্কা পপ। ফাংশনগুলি "কল" নির্দেশনার দ্বারা যা push করা হয়েছে তা কেবলমাত্র অতীতের শুরুতে ক্রমানুসার ক্রমে তাদের আর্গুমেন্টগুলিতে ঠিকানাগুলি বরাদ্দ করবে।

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

স্ট্যান্ডার্ডের আগে বিদ্যমান ভাষাটির বেশিরভাগ বাস্তবায়নে, একটি ফাংশন লিখতে পারে:

int foo(x,y) int x,y
{
  printf("Hey\n");
  if (x)
  { y+=x; printf("y=%d\n", y); }
}

এবং যেমন foo(0) বা foo(0,0) , প্রাক্তন সামান্য দ্রুত সঙ্গে। যেমন foo(1); হিসাবে এটি কল করার চেষ্টা foo(1); সম্ভবত স্ট্যাক দুর্নীতিগ্রস্ত হবে, কিন্তু ফাংশন বস্তু y ব্যবহার না হলে এটি পাস করার কোন প্রয়োজন ছিল। যেমন শব্দার্থবিদ্যা সমর্থন সমস্ত প্ল্যাটফর্মের উপর বাস্তব ছিল না, এবং বেশিরভাগ ক্ষেত্রে যুক্তি প্রমাণীকরণের সুবিধাগুলি খরচ অতিক্রম করে, তাই স্ট্যান্ডার্ডগুলিকে যে বাস্তবায়নগুলি সেই প্যাটার্নটিকে সমর্থন করার যোগ্য হতে পারে না, তবে যারা প্যাটার্নকে সমর্থন করতে পারে সুবিধামত তাই করে ভাষা প্রসারিত।







function-pointers