লেকচার ১২ – সি প্রোগ্রামিং – মেমোরি অপারেশন এবং পয়েন্টার

লেকচার ১২ – সি প্রোগ্রামিং – মেমোরি অপারেশন এবং পয়েন্টার


আমরা জানি ভেরিয়েবলের মধ্যে কোন একটা মান জমা রাখা যায়। যেমন কোন একটি ভেরিয়েবল x যেটার টাইপ int সেটা আমরা ডিক্লেয়ার করি এভাবে-
int x;
এখন এই ভেরিয়েবলের মধ্যে যে কোন ইন্টেজার মান রাখতে পারব এভাবে-
x=100;
যদিও আমরা 100 মানটি রাখার জন্য x নামের একটি ভেরিয়েবল ডিক্লেয়ার করেছি কম্পিউটার যখন আমাদের প্রোগ্রামটি রান করবে তখন আসলে এর জন্য একটা নির্দিষ্ট স্থান মেমোরিতে নির্ধারন করা হবে্। ধরা যাক এই মেমোরির লোকেশন হচ্ছে 40000। তাহলে মেমোরির 40000 লোকেশনে 4 বাইট জুড়ে 100 সখখ্যাটি রাখা হবে। আমরা এখানে ধরে নিচ্ছি আমরা যে কম্পিউটার ব্যবহার করছি সেটা ইন্টাজার রা্খার জন্য 4 বাইট মেমোরি ব্যবহার করে। এটা যদি ধরে না নিয়ে আমরা বের করতে চাই তাহলে এভাবে বের করা যায় sizeof ব্যবহার করে-
int int_size = sizeof(int);
এখন আমরা x ভেরিয়েবলের লোকেশন যেটা আমরা 40000 ধরে নিয়েছি সেটা বের করতে চাই তাহলে আমরা & ব্যবহার করে সেটা করতে পারি। ভেরিয়েবলের নামের পূর্বে & দিলেই সেটার অ্যাড্রেস্‌ পাব-
printf("%d", &x);
এক্ষেত্রে লোকেশন কনসোলে 40000 (আসলে সত্যিকারের লোকেশন যেটা সেটা- কিন্তু সুবিধার জন্য আমরা 40000 ধরে নিচ্ছি) প্রিন্ট হবে।
এভাবে প্রিন্ট না করে যদি 40000 মানটি আমরা একটা ভেরিয়েবলের মধ্যে জমা রাখতে চাই তাহলে আমরা পয়েন্টার টাইপের একটা ভেরিয়েবলের মধ্যে রাখতে পারি। যেহেতু x একটি int টাইপের ভেরিয়েবল এটার লোকেশন রাখার জন্য আমরা সাধারনত int টাইপের পয়েন্টার ব্যবহার করব। অন্য লেরিয়েবলের মতই এটার জন্য একটা নাম ঠিক করে হবে। আমরা যদি ঠিক px করি তাহলে আমরা এভাবে সেই ভেরিয়েবলটি ডিক্লেয়ার করতে পারি-
int *px;
ভেরিয়েবলের নামের পূর্বে * চিহ্ন দিয়ে‌ আমরা বুঝিয়েছি যে এটা একটা পয়েন্টার টাইপের ভেরিয়েবল। এবং int দিয়ে বুঝিয়েছি আমরা ইন্টেজার টাইপের ভেরিয়েবলের অ্যাড্রেস রাখতে পারব।
এখন আমরা px ভেরিয়েবলের মধ্যে যদি x ভেরিয়েবলের লোকেশন রাখতে চাই তাহলে এভাবে লিখতে পারি-
px = &x;
এখন px এর মান যদি আমরা প্রিন্ট করি তাহলে আমরা 40000 পাব।
এখন যদি px ব্যবহার করে x এর মান পেতে চাই তাহলে আমরা এভবে লিখতে পারি-
int v = *px;
তাহলে v এর মধ্যে 100 মানটি জমা হবে যেটা আমরা x এর মধ্যে পূর্বে জমা রেখেছি। পয়েন্টার ভেরিয়েবলের নামের পূর্বে * ব্যবহার করে পয়েনটার যে লোকেশন পয়েন্ট করছে সেই লোকেশনে যে মান আছে সেটা পাওয়া যায়।
এখন আমরা উপরে শেখা বিষয়গুলো একটা প্রোগ্রামে ব্যবহার করিঃ
#include "stdio.h"

int main()
{
    int x;
    x=100;

    printf("Address of x = %dn", &x);

    int *px;

    px = &x;

    printf("px = %dn", px);

    printf("Value of x = %dn", *px);

    return 0;
}
এখন আমি প্রগ্রামটা প্রথমবার রান করে নিচের মত আউটপুট পাই।
Address of x = 13434088
px = 13434088
Value of x = 100
দ্বিতীয়বার রান করে নিচের মত আউটপুট পাই।
Address of x = 9959872
px = 9959872
Value of x = 100
সুতরাং দেখা যাচ্ছে প্রতিবার রান করার জন্য আমরা x এর ভিন্ন ভিন্ন লোকেশন পাচ্ছি যেটা আমরা শুরুতে 40000 ধরে নিয়েছিলাম। বাস্তবে্‌ আমরা কখনই একটা ভেরিয়েবলের অ্যাড্রেস আগে থেকে ধরে নিতে পারব না (অবশ্য আমরা যদি অপারেটিং সিস্টেম প্রোগ্রাম না লিখি। অপারেটিং সিস্টেম লেখার জন্য সি ল্যাংগুয়েজই ব্যবহার করা হয়। উইন্ডোজ, লিন্যাক্স ইত্যাদির মূল অংশ বা কার্নেল সি ল্যাংগুয়েজ ব্যবহার করে লেখা হয়েছে।)।
উপরের প্রগ্রামটা ভালভাবে বোঝার জন্য নিজে নিজে কয়েকবার রান করে দেখুন। কিছু পরিবর্তন করে পরীক্ষা নিরিক্ষা করতে পারেন।

পয়েন্টার এবং অ্যারে

প্রথমে আমরা একটা ইন্টেজার ভেরিয়েবলের অ্যারে নিয়ে একটা প্রোগ্রাম লিখি-
#include "stdio.h"

int main()
{
    /* Step 0 */
    int numbers[3];

    numbers[0] = 56;
    numbers[1] = 25;
    numbers[2] = 87;

    int *px;

    /* Step 1 */
    px = numbers;
    printf("First value of px = %dn", px);
    printf("First value of variable pointed by px = %dn", *px);

    /* Step 2 */
    px = &numbers[0];
    printf("Second value of px = %dn", px);
    printf("First value of variable pointed by px = %dn", *px);

    /* Step 3 */
    px = &numbers[1];
    printf("Third value of px = %dn", px);
    printf("Third value of variable pointed by px = %dn", *px);

    /* Step 4 */
    px = &numbers[2];
    printf("Last value of px = %dn", px);
    printf("Last value of variable pointed by px = %dn", *px);

    return 0;
}
এখানে প্রথমে (Seep 0) আমরা একটা ইন্টেজার অ্যারে numbers ডিক্লেয়ার করেছি এবং ৩ টা সংখ্যা অ্যারেটার তিনটা স্থানে রেখেছি।
এরপর (Step 1) আমরা numbers অ্যারে এর অ্যাড্রেস আমরা পয়েসটার টাইপ ভেরিয়েবল px এর মধ্যে রেখেছি। এখানে লক্ষ্য করুন numbers এর পূর্বে আমরা & চিহ্ন ব্যবহার করি নাই। এর কারন হচ্ছে অ্যারে টাইপ ভেরিয়েবল নিজেই ওই অ্যারে ভেরিয়েবলের লোকেশন জমা রাখে।
এরপর (Step 2) আমরা numbers[0] এর অ্যাড্রেস px এর মধ্যে রেখেছি। এক্ষেত্রে & চিহ্ন ব্যবহার করতে হয়েছে। মনে রাখার সুবিধার জন্য আপনি লক্ষ করবেন printf("%d", numbers[0]) এর আউটপুট হবে 56 কিন্তু printf("%d", numbers) এর আউটপুট হবে numbers এর অ্যাড্রেস। যেক্ষেত্রে মান প্রিন্ট হবে সেক্ষেত্রে & চিহ্ন ব্যবহার করতে হবে অ্যাড্রেস বের করার জন্য।
এরপর বাকি দুইটি মানের জন্য (Step 3,4) একইভাবে কোড লেখা হয়েছে।
এই প্রোগ্রামটি আমি দুইবার রান করেছি। প্রথমবারের আ্উটপুট হচ্ছে-
First value of px = 10222376
First value of variable pointed by px = 56
Second value of px = 10222376
Second value of variable pointed by px = 56
Third value of px = 10222380
Third value of variable pointed by px = 25
Last value of px = 10222384
Last value of variable pointed by px = 87
আমরা দেখতে পাচ্ছি numbers এবং &numbers[0] এর মান একই। সুতরাং আমরা বুঝতে পারছি যে, কোন একটি অ্যারে ভেরিয়েবল আসলে এর প্রথম আইটেম এর অ্যাড্রেস নিজের মধ্যে জমা রাখে- যেটা এক্ষেত্রে 10222376. এরপর আমরা ২য় আইটেম (numbers[1]) এর অ্যাড্রেস প্রিন্ট করে পাচ্ছি 10222380. এই অ্যাড্রেসটা আসলে numbers[0] এর অ্যাড্রেসের চেয়ে 4 বেশি। যেহেতু আমরা ইন্টেজার এর অ্যারে নিয়ে কাজ করছি এবং আমরা জানি ইন্টেজার সাধারনত 4 বাইট যায়গা নেয় সুতরাং প্রথম আইটেম রাখার জন্য 4 বাইট যায়গা লেগেছে এবং 10222376, 10222377, 10222378, 10222379 এই চারটা লোকেশন প্রথম সংখ্যাটা রাখার জন্য ব্যবহার করার পর ২য় সংখ্যাটা 10222380 থেকে রাথা শুরু হরা হয়েছে। অ্যাড্রেস হিসাবে শুরুটা জানলেই চলে। কারণ পরবর্তী লোকেশন আমরা সহজেই বের করতে পারি যেগুলো হবে- 10222381, 10222382 এবং 10222383.
এখন এটুকু বুঝে থাকলে আপনি সহজেই ৩য় সংখ্যাটির (numbers[2]) অ্যাড্রেস কত হবে বের করতে পারবেন – যেটা হবে 10222383 এর পরবর্তী লোকেশন 10222384। এবং প্রোগ্রামের আউটপুটে দেখুন সেটাই এসেছে।
এবার ২য়বার রান করার পর আউটপুট দেখুনঃ
First value of px = 7536156
First value of variable pointed by px = 56
Second value of px = 7536156
Second value of variable pointed by px = 56
Third value of px = 7536160
Third value of variable pointed by px = 25
Last value of px = 7536164
Last value of variable pointed by px = 87
প্রথম আইটেমের অ্যাড্রেস হচ্ছে 7536156. সুতরাং ২য় আইটেম এর লোকেশন হবে 7536156 + 4 = 7536160 এবং ৩য় আইটেম এর লোকেশন হবে 7536160 + 4 = 7536164.
এবার একটা ছোট পরীক্ষা হয়ে যাক।আমি প্রোগ্রামটা ৩য় বার রান করে আংশিক আউটপুট দিচ্ছি।
First value of px = 16054796

Last value of variable pointed by px = 87
আপনি এই দুই লাইনের মাঝের ৬ লাইন কি হবে বের করুন। উত্তর লেসনের শেষে দেয়া আছে মিলিয়ে নিন (প্রশ্ন ১)।

পয়েন্টার গনণা (pointer arithmetic)

উপরে আমরা দেখেছি পয়েন্টারের অ্যাড্রেস এর সাথে একটি সংখ্যা যোগ করে কিভাবে পরবর্তী আইটেম এর অ্যাড্রেসটি আমরা পেতে আরি। এখন এটা আমরা প্রোগ্রাম থেকে আরও সহজে করতে পারি।
প্রথমে আমরা অ্যারেটির অ্যাড্রেস px এর মধ্যে রাখি-
px = numbers;
এটা আমরা প্রথম আইটেম এর অ্যাড্রেস রেখেও করে পারি-
px = &numbers[0];
এবার যদি আমরা ২য় আইটেমটি পেতে চাই তাহলে লিখতে পারি-
px2 = px+1;
এখনে px2 এর মান হবে px এর মানের থেকে 4 বেশি। এক্ষেত্রে কম্পাইলার সয়ংক্রিয়ভাবে বুঝে নিবে যে তাকে 4 যোগ করতে হবে পরবর্তী আইটেম পাওয়ার জন্য। কারণ ইন্টেজার আইটেম 4 বাইট যায়গা নেয়।
এবার যদি আমরা ৩য় আইটেমটি পেতে চাই তাহলে লিখতে পারি-
px3 = px+2;
আমরা যদি px3 এর অ্যাড্রেস দেখি তাহলে দেখব সেটা px হতে 4×2=8 বেশি। এভাবে আমরা যেকোন আইটেমের অ্যাড্রেস পেতে পারি px এর সাথে যোত করে।
এবার প্রোগ্রাম এর কোড দেখুনঃ
#include "stdio.h"

int main()
{
    int numbers[3];

    numbers[0] = 56;
    numbers[1] = 25;
    numbers[2] = 87;

    int *px, *px2, *px3;

    px = numbers;
    printf("Value of px (numbers) = %dn", px);
    printf("Value of numbers[0] = %dn", *px);

    px2 = px+1;
    printf("Value of px (&numbers[0]) = %dn", px2);
    printf("Value of *px2 = %dn", *px2);

    px3 = px+2;
    printf("Value of px (&numbers[0]) = %dn", px3);
    printf("Value of *px2 = %dn", *px3);

    return 0;
}
এটা রান করার পর আমি নীচের আউটপুক পেয়েছিঃ
Value of px (&numbers[0]) = 14940932
Value of numbers[0] = 56
Value of px (&numbers[0]) = 14940936
Value of *px2 = 25
Value of px (&numbers[0]) = 14940940
Value of *px2 = 87
এখন আমরা জানি numbers হচ্ছে প্রথম অ্যারেটির প্রথম আইটেমের অ্যাড্রেস। তাহলে আমরা numbers[1] এভাবে না লিখে যদি numbers + 1 এভাবে লিখি তাহলে কি হবে? নিচের প্রগ্রামে আমরা সেটাই করেছি এবং কাংখিত ফলাফলও পেয়েছিঃ
#include "stdio.h"

int main(int argc, char* argv[])
{
    int numbers[3];

    numbers[0] = 56;
    numbers[1] = 25;
    numbers[2] = 87;

    int *px, *px2;

    px = numbers;
    printf("Value of px (&numbers[0]) = %dn", px);
    printf("Value of numbers[0] = %dn", *px);

    px2 = numbers+1;
    printf("Value of px [numbers+1] = %dn", px2);
    printf("Value of *px2 = %dn", *px2);

    printf("*(numbers+2) = %dn", *(numbers+2));

    return 0;
}
শেষ printf লক্ষ্য করলে দেখবেন আমরা প্রথমে numbers+2 লিখে ৩য় আইটেমের অ্যাড্রেস বের করেছি। তারপর তার পূর্বে * চিহ্ন দিয়ে সেই অ্যাড্রেসে যে মান আছে সেটা প্রিন্ট করতে বলেছি। সুতরাং এক্ষেত্রে 87 পিন্ট হওয়ার কথা। আউটপুট দেখুনঃ
Value of px (&numbers[0]) = 12908512
Value of numbers[0] = 56
Value of px [numbers+1] = 12908516
Value of *px2 = 25
*(numbers+2) = 87
আশা করি এ পর্যন্ত সবকিছু বুঝতে পেরেছেন। যদি প্রথমবার পয়েন্টার নিয়ে কাজ করেন তাহলে চুরো জিনিসটা বুঝতে একচু সময় লাগতে পারে। আমার প্রা্য় ১ বছর লেগেছিল এটা পুরোপুরি বুঝতে এবং মনে রাখতে – শেখা শুরু করার পর।

স্ট্রাকচার এবং অ্যারে

প্রথমে আমরা স্ট্রাকচার ব্যবহার করে একটা ছোট প্রোগ্রাম লিখিঃ
#include "stdio.h"

struct Student
{
    int roll;
    float cgpa;
};

int main(int argc, char* argv[])
{

    Student stu1;

    stu1.roll = 10;
    stu1.cgpa = 3.92;
    printf("Roll = %d, CGPA =  %0.2fn", stu1.roll, stu1.cgpa);

    return 0;
}
এখানে আমরা Student নামে একটা স্ট্রাকচার ডিফাইন করেছি এবং প্রোগ্রামে সেটা ব্যবহার করেছি। প্রথমে একটা ভেরিয়েবল ডিক্লেয়ার করে সেটার মধ্যে মান রেখেছি এবয় পরে সেটা প্রিন্ট করেছি।
আউটপুট হবে-
Roll = 10, CGPA =  3.92
এবার আমরা পয়েন্টার দিয়ে সেটা করতে চাই। পূর্বের মদই আমরা একটা পয়েনটার টাইপ ভেরিয়েবল ডিক্লেয়ার করব।
Student *pstu1;
এবার এই পয়েন্টারের মধ্যে পুর্বের ভেরিয়েবলের অ্যাড্রেস রাখব-
pstu1 = &stu1;
এবার যদি আমরা pstu1 ব্যবহার করে stu1 এর মানগুলো পেতে চাই তাহলে “.” এর পরিবর্তে “->” ব্যবহার করে মানগুলো পেতে পারি- মানগুলো হবে pstu1->roll এবং pstu1->cgpa । এবার পুরো প্রোগ্রাম দেখুনঃ
#include "stdio.h"

struct Student
{
    int roll;
    float cgpa;
};

int main(int argc, char* argv[])
{

    Student stu1;

    stu1.roll = 10;
    stu1.cgpa = 3.92;

    Student *pstu1;

    pstu1 = &stu1;

    printf("Roll = %d, CGPA =  %0.2fn", pstu1->roll, pstu1->cgpa);

    return 0;
}
এখানে আমরা stu1 ব্যবহার করে মান অ্যাসাইন করে pstu1 ব্যবহার করে অ্যাক্সেস করেছি মানগুলো।
উল্টোটাও করতে পারেন। pstu1 ব্যবহার করে মান অ্যাসাইন করে stu1 ব্যবহার করে অ্যাক্সেস করতে পারেন। ফলাফল একই হবে। নিচের প্রোগ্রামটি দেখুনঃ
#include "stdio.h"

struct Student
{
    int roll;
    float cgpa;
};

int main(int argc, char* argv[])
{

    Student stu1;
    Student *pstu1;

    pstu1 = &stu1;

    pstu1->roll = 10;
    pstu1->cgpa = 3.92;

    printf("Roll = %d, CGPA =  %0.2fn", stu1.roll, stu1.cgpa);

    return 0;
}
স্ট্রাকচার এর অ্যারেও পয়েন্টার ব্যবহার করে অ্যাক্সের করা যায় পূর্বের মতই।
#include "stdio.h"

struct Student
{
    int roll;
    float cgpa;
};

int main(int argc, char* argv[])
{

    Student stu[2];

    stu[0].roll = 10;
    stu[0].cgpa = 3.92;

    stu[1].roll = 16;
    stu[1].cgpa = 3.57;

    Student *pstu1;
    pstu1 = stu;

    printf("Roll = %d, CGPA =  %0.2fn", pstu1->roll, pstu1->cgpa);

    pstu1 = stu + 1;

    printf("Roll = %d, CGPA =  %0.2fn", pstu1->roll, pstu1->cgpa);

    printf("stu = %d, stu+1=%dn", stu, stu+1);

    return 0;
}
আউটপুট হবে এরকমঃ
Roll = 10, CGPA =  3.92
Roll = 16, CGPA =  3.57
stu = 13499656, stu+1=13499664
যেহেতু ইন্টেজার ব্যবহার করে পুরো ব্যাপারটি একবার আলোচনা করেছি স্ট্রাকচার ব্যবহার করে আর আলোচনা করলাম না। শুধু একটা ব্যাপর লক্ষ করুন ২য় অ্যাড্রেসটি প্রথম অ্যাড্রেসের থেকে ৮ (আট) বেশি। কারণ Student স্ট্রাকচার এর জন্য মেমোরিতে ৮ বাইট যায়গা লাগে- চার বাইট ইন্টেজারের জন্য চার বাইট ফ্লোট নাম্বারের জন্য। যার কারনে ২য় আইটেমটির অ্যাড্রেস হবে প্রথম আইটেমের অ্যাড্রেসের থেকে ৮ বেশি। sizeof(Student) দিয়ে আপনি Student স্ট্রাকচারটির একটি আইটেম কত যায়গা নিচ্ছে সেটা ের করতে পারবেন।
পয়েন্টার নিয়ে আলোচনা এই পর্যন্তই। যারা প্রথমবার করছেন তারা সবগুলো উদাহরন ভালভাবে লক্ষ্য করুন এবং প্রতিটি বেশ কয়েকবার অনুশীলন করুন। বাইসাইকেল যেমন বই পড়ে শেখা যায় না শুধু কোনটা কি কাজ করে সেটা জানা যায় প্রোগ্রামিংও তেমনি পড়ে সামান্যই শেখা যায়। সি কোর্সটি আয়ত্ব করতে চাইলে মোটামুটি ১০০ থেকে ২০০ ঘন্টা সময় কোড লিখতে হবে। যে কোড দেখতে খুব সাধারণ মনে হয় সেটাও লেখার মাধ্যমে অনুশীলন করতে হবে।
আমার সাথে থাকার জন্য সবাইকে ধন্যবাদ। সবার প্রোগ্রামিং ক্যারিয়ারের সেকেন্ড ডিফারেন্সিয়াল নেগেটিভ হোক এই কামনায় সি কোর্স থেকে বিদায় নিচ্ছি।
প্রশ্ন ১ এর পুরো আউটপুট:
First value of px = 16054796
First value of variable pointed by px = 56
Second value of px = 16054796
Second value of variable pointed by px = 56
Third value of px = 16054800
Third value of variable pointed by px = 25
Last value of px = 16054804
Last value of variable pointed by px = 87
এই লেসনে কিছু বিষয় আমি ইচ্ছা করে একটু অন্যভাবে লিখেছি সহজভাবে বোঝার সুবিধার জন্য- রেফারেন্স হিসাবে ব্যবহারের জন্য না।

0 মন্তব্য(গুলি):