পয়েন্টার (Pointer)

Jun 5, 2013

প্রায় সব নবীন প্রোগ্রামারদের জন্যই সম্ভবত পয়েন্টার একটা ভয়ংকর অভিজ্ঞতা! এটি না হওয়ারও আসলে কোন কারণ নেই। পয়েন্টার অনেক বেশি মাত্রায় কনফিউশন তৈরি করে। এটি কি এবং কিভাবে কাজ করে সেই সম্পর্কে স্পষ্ট ধারণা না থাকলে এটি ব্যবহার করাও বেশ দুরূহ হয়ে দাঁড়ায়।

পয়েন্টার হচ্ছে একটা ভেরিয়েবল। হ্যাঁ, int, char, double এগুলো যেমন ভেরিয়েবল, পয়েন্টারও তেমনি একটি ভেরিয়েবল। int এ থাকে ১৬/৩২ বিট পূর্ণ সংখ্যা, double এ থাকে দশমিক সংখ্যা, আর পয়েন্টারে থাকে একটা এড্রেস। শুরু হয়ে গেল কনফিউশন! :P

কনফিউশন দূরীকরণ শুরু করি। ব্যাপারটাকে ভিজুয়ালাইজ করতে পারলে হয়তো একটু সহজ হবে বুঝতে পারা, তাই বাস্তব কিছুর সাথে সাদৃশ্য খোঁজার চেষ্টা করা যাক। ধরা যাক, ভেরিয়েবল হচ্ছে একটি বাক্স। বাক্সে কিছু জিনিস রাখা যায়। অর্থাৎ বাক্সের মেমরি থাকে। ভেরিয়েবলেরও কিছু মেমরি থাকে। ৮ বিট, ১৬ বিট, ৩২ বিট ইত্যাদি বিভিন্ন পরিমাণের মেমরি থাকে। অতএব ভেরিয়েবল হল একটি বাক্স যার নির্দিষ্ট মেমরি থাকে। ভেরিয়েবলের যে নাম, সেটি হল বাক্সের নাম। আমার কাছে অসংখ্য বাক্স আছে। চেনার জন্য প্রতিটা বাক্সের নাম থাকবে।

int i;

আমি i নামে একটা int টাইপের ভেরিয়েবল ডিক্লেয়ার করলাম। অর্থাৎ, অসংখ্য মেমরি ব্লক বা বাক্স থেকে একটা বাক্স আমার জন্য বরাদ্দ হয়ে গেল। বাক্সটার ধারণ ক্ষমতা ১৬/৩২ বিট (সিস্টেমভেদে) এবং এর নাম দিলাম i । আমি এখানে যদি 5 রাখতে চাই তাহলে লিখব i = 5; সহজ ব্যাপার। বাক্সের মধ্যে 5 ঢুকিয়ে দিলাম। যেখানেই এটা ব্যবহার করতে হবে আমি i লিখলেই হয়ে যাবে।

এখন আমার কাছে তো অনেক মেমরি আছে, অনেক বাক্স আছে। ধরলাম বাক্সগুলো একটা বিশাল গোডাউনের মধ্যে রাখা আছে। বাক্সগুলো সুন্দর করে একটার পর একটা সাজিয়ে রাখা আছে যাতে সহজেই কোন একটা বাক্স কোথায় আছে জানা থাকলেই সেটা খুঁজে পাওয়া যায়। এই যে বললাম ‘কোথায় আছে’ এর মানেই হল প্রতিটার বাক্সের একটা ঠিকানা/এড্রেস আছে। গোডাউনের কোথায় বাক্সটা আছে, কত নাম্বার ব্লকে, কত নাম্বার লাইনে আছে এরকম কোন একটা ঠিকানা আছে। এভাবে পুরো গোডাউনে যত বাক্স আছে সবগুলোর জন্যই একটা করে ঠিকানা আছে। আমি যদি বাক্সটার নাম নাও জানি, আমি বাক্সটার ঠিকানা জানলে সেখানে গেলেই বাক্সটাকে পেয়ে যাব।

অর্থাৎ আমরা যে ভেরিয়েবল ডিক্লেয়ার করি, তখন যে মেমরি আমাদের জন্য বরাদ্দ হয়ে যায়, তার একটা এড্রেস আছে। আমরা চাইলে ভেরিয়েবলের নাম দিয়েও সেটা ব্যবহার করতে পারি, আবার চাইলে এড্রেস দিয়েও ব্যবহার করতে পারি কারণ এড্রেসে গেলে ঐ ভেরিয়েবলটাই পাওয়া যাবে।

এখন এড্রেস তো একটা তথ্য। মেমরিতে প্রতিটা মেমরি ব্লকের জন্য এড্রেস নির্দিষ্ট। এই এড্রেস যদি আমি ব্যবহার করতে চাই তাহলে তাকে একটা ভেরিয়েবলের মধ্যে রাখতে হয়। সেই ভেরিয়েবলটাই হল পয়েন্টার। অন্য সব ভেরিয়েবলের যেমন সাইজ নির্দিষ্ট তেমনি পয়েন্টারেরও সাইজ নির্দিষ্ট। আমরা যে অপারেটিং সিস্টেম ব্যবহার করি, ধরা যাক উইন্ডোজ, এটায় যে বলা থাকে ৩২ বিট বা ৬৪ বিট সেটা আসলে পয়েন্টারের সাইজ। অর্থাৎ একেকটা এড্রেসকে ভেরিয়েবলে স্টোর করে রাখতে অত বিট মেমরি লাগে।

আমরা পয়েন্টার ডিক্লেয়ার যখন করি তখন যে ভেরিয়েবলের মধ্যে এড্রেস রাখবো তার সামনে একটা * দিয়ে দেই। *ptr মানে হল “ptr” নামে একটা ভেরিয়েবল তৈরি হয়েছে যেখানে কোন একটা এড্রেস স্টোর করে রাখা যাবে। এটা কিন্তু বেশ গুরুত্বপূর্ণ। ptr কোন এড্রেস না, এটা একটা ভেরিয়েবল যেখানে একটা এড্রেস আছে! এড্রেসকে হেক্সাডেসিমেলে প্রকাশ করা হয়। printf( ) দিয়ে এড্রেস আউটপুট দিলে দেখা যাবে একটা হেক্সাডেসিমেল নাম্বার দেখাচ্ছে। কিভাবে করা যায় সেটায় পরে আসি।

তাহলে যা বুঝলাম তা হল যে *ptr লিখলে ptr নামে একটা ভেরিয়েবল তৈরি হয়, যেমন তৈরি হয়েছিল i নামে একটা ভেরিয়েবল। এই ptr ভেরিয়েবলের মধ্যে একটা এড্রেস থাকবে। এখন কিসের এড্রেস এখানে থাকবে সেটা কম্পিউটারকে বলে দিতে হয়। নাহলে সে কাজ করতে পারবে না। তাই যদি আমি একটা ইন্টিজারের পয়েন্টার ডিক্লেয়ার করি তাহলে লিখব, int *ptr; তাহলে যেটি হবে তা হল ptr নামে একটা পয়েন্টার (মানে এড্রেসের ভেরিয়েবল) তৈরি হবে যেটায় শুধুমাত্র একটা int টাইপ ভেরিয়েবলের এড্রেস থাকতে পারবে। এখানে আরেকটা গুরুত্বপূর্ণ বিষয় খেয়াল করা লাগবে। যখন আমি ptr ডিক্লেয়ার করলাম, তখন কিন্তু কোন ইন্টিজারের জন্য মেমরি বরাদ্দ হয়নি যেমনটি হয় যদি আমি লেখি int i;

int i; লিখলে i নামের একটা int ভেরিয়েবল তৈরি হয়। সেখানে ৩২ বিট মেমরি বরাদ্দ হয়েছে। কিন্তু যখন ডিক্লেয়ার করলাম তখন কি সেই মেমরি এর ভিতরে কিছু আছে? নেই। কারণ কিছু রাখিনি আমরা। (বাস্তবে গার্বেজ ভেলু থাকে, সেইটা আলোচনার বিষয় না। ধরা যাক কিছু নেই।) যেটি হয়েছে তা হল আমি কম্পিউটারকে বললাম যে আমার জন্য i নামের একটা ভেরিয়েবল তৈরি কর যেখানে আমি int টাইপের (৩২ বিটের) কিছু রাখতে পারব।

একইভাবে যখন আমি লিখলাম int *ptr; তখন ptr নামের একটা ভেরিয়েবল তৈরি হল। সেখানে ৩২/৬৪ বিট মেমরি বরাদ্দ হয়েছে। কিন্তু সেই মেমরি এর ভিতরে কিছু নেই। যেমনটি ছিল না int i এর ক্ষেত্রে। এই যে মেমরি বরাদ্দ হয়েছে, যেখানে এখনও কিছু নেই, এটা কিন্তু কোন ইন্টিজারের মেমরি না! এটা এড্রেসের জন্য বরাদ্দ হয়েছে। অনেকেই ভাবে আমি তো ptr ডিক্লেয়ার করলাম। তাহলে কেন আমি এখানে একটা ইন্টিজার রাখতে পারব না। তার কারণ হল যখন আমি ডিক্লেয়ার করলাম তখন আমি একটা এড্রেসের জন্য মেমরি বরাদ্দ করেছি। এটা কোন ইন্টিজারের মেমরি না। এখানে একটা এড্রেস থাকবে যেই এড্রেস কোন একটা int এর এড্রেস। কিন্তু এখনও এখানে কোন এড্রেস নেই। ঠিক আগের int i এর মত। এই অংশটুকু বুঝতে অসুবিধা হলে বারবার পড়ে দেখ। এই ব্যাপারটা ক্লিয়ার হওয়া অনেক জরুরী। অন্যথায় পয়েন্টার কি সেটাই বোঝা হবে না। প্রয়োগ তো পরের কথা!

ধরে নিচ্ছি এখন আমরা পয়েন্টার কি এতটুকু অন্তত জানি। :) এখন দেখা যাক এটা নিয়ে কিভাবে কাজ করা হয়। একেবারে বেসিক জিনিসপত্র। নিচের অংশটুকু দেখা যাক।

int *ptr1, *ptr2;
int value1, value2;
ptr1 = &value1;
printf( "%p ", ptr1 );
ptr2 = &value2;
printf( "%p\n", ptr2 );
ptr1 = &value2;
printf( "%p ", ptr1 );
ptr2 = &value1;
printf( "%p", ptr2 );

প্রথম লাইনে ptr1 এবং ptr2 নামে দুটি পয়েন্টার ভেরিয়েবল ডিক্লেয়ার করলাম যারা কোন int এর এড্রেস রাখতে পারবে। এরপরের লাইনে দুইটা int ভেরিয়েবল ডিক্লেয়ার করলাম value1, value2। এরপরের লাইনে & অপারেটর ব্যবহার করা হয়েছে। এই অপারেটর দিয়ে কোন একটা ভেরিয়েবলের এড্রেস পাওয়া যায়। value1 হল একটা int ভেরিয়েবলের নাম। সেটার এড্রেস আমরা জানি না। আর আগেই বলেছি ptr1 কিন্তু কোন এড্রেস না। এটা এড্রেস রাখার একটা ভেরিয়েবল। এখানে আমরা value1 এর এড্রেস রাখতে চাই। value1 এর এড্রেস পাওয়ার জন্য আমরা লিখলাম &value1 । শুধু value1 একটা ভেরিয়েবল, যখনই তার সামনে & বসালাম তখনই সেটা হয়ে গেল value1 এর এড্রেস। তাহলে এখন ptr1 এর মধ্যে value1 এর এড্রেস রাখলাম। কেউ যদি এটা লিখে ptr1 = value1 তাহলেই ওয়ার্নিং দেখাবে, কারণ value1 হল ভেরিয়েবলের না, এখানে রাখতে হবে এড্রেস।

এরপরের লাইনে ptr1 এর মধ্যে যে এড্রেস আছে সেটিকে আউটপুট দেওয়া হল। এড্রেসকে হেক্সাডেসিমেলে প্রকাশ করা হয় এবং ইন্টিজার আউটপুট দিতে যেমন %d ব্যবহার করা হয় তেমনি এড্রেস আউটপুট দিতে %p ব্যবহার করা হয়। আউটপুটে একটা হেক্সাডেসিমেল নাম্বার দেখা যাবে যেটিই আসলে value1 এর এড্রেস। এর পরেরলাইনে একইভাবে ptr2 এর মধ্যে value2 এর এড্রেস রাখা হচ্ছে এবং তার পরের লাইনে সেটিকে আউটপুট দেওয়া হচ্ছে।

এরপরের চার লাইনে শুধু উলটিয়ে দেওয়া হয়েছে। ptr1 এ রাখা হয়েছে value2 এর এড্রেস এবং ptr2 এ রাখা হয়েছে value1 এর এড্রেস। আমার কম্পিউটারে এই কোডের আউটপুট এরকমঃ

0022FF14 0022FF10
0022FF10 0022FF14

এই কোডটার উদ্দেশ্য এটা বোঝানো যে যখন আমি একটা int ভেরিয়েবল ডিক্লেয়ার করি তখন কোন একটা জায়গায় একটা মেমরি বরাদ্দ হয়ে যায় এবং যেখানে মেমরি বরাদ্দ হয়েছে সেই এড্রেসটা নির্দিষ্ট। দুইটি ভেরিয়েবল দুই জায়গায় বরাদ্দ হয়েছে। দুইটির আলাদা আলাদা নির্দিষ্ট এড্রেস আছে। আমি কখন কোন পয়েন্টারে কোন এড্রেস রাখবো এবং কিভাবে সেটা ব্যবহার করব তা সম্পূর্ণ আমার ব্যাপার। উপরের কোডে আমি একবার ptr1 এ value1 এর এড্রেস রাখলাম আরেকবার ptr2 এ রাখলাম। এড্রেস কিন্তু একই আছে। আউটপুটেই তা দেখা যাচ্ছে। শুধু দুইবার দুই ভেরিয়েবলে এড্রেসটা রাখলাম। আশা করি ব্যাপারটা পরিষ্কার করতে পেরেছি।

একটা পয়েন্টারে যে এড্রেস আছে আমরা যদি সেই এড্রেসের ভেরিয়েবলটা ব্যবহার করতে চাই তাহলে পয়েন্টারের আগে একটা * লেখি। যেমন উপরের ক্ষেত্রে আমরা ptr এর মধ্যে যখন value1 এর এড্রেস রাখলাম তখন যদি আমরা value1 কে ptr1 এর মাধ্যমে ব্যবহার করতে চাই তাহলে লিখবো *ptr1 । যেমন যদি value2 এর মধ্যে value1 রাখতে চাই, তাহলে লিখবো, value2 = *ptr1; যদি value1 এর মধ্যে value2 রাখতে চাই এড্রেসের মাধ্যমে তাহলে লিখবো, *ptr1 = value2; অর্থাৎ &value1 লিখলে যেমন value1 এর এড্রেস পাওয়া যায় তেমন *ptr1 লিখলে ptr1 এ যে এড্রেস আছে সেই এড্রেসে অবস্থিত ভেরিয়েবল পাওয়া যায়।

আরেকটা বিষয় জানা থাকা দরকার। ptr = 0 বা ptr = NULL যদি লেখা হয় তাহলে কি হয়? কোন এড্রেস 0 বা NULL মানে হচ্ছে সেটি আসলে কোন কিছুকেই নির্দেষ করছে না। 0 কোন এড্রেস মেমরিতে নেই। তাই যখনই প্রোগ্রাম এড্রেস 0 পাবে তখন সে বুঝবে যে এটা আসলে কোন কিছুকেই নির্দেশ করছে না, বা এখানে আসলে কোন এড্রেস নেই। অনেক ফাংশন NULL পয়েন্টার রিটার্ন করে। অর্থাৎ তার হয়তো কিছু একটা করে সেটার জন্য একটা এড্রেস রিটার্ন করার কথা ছিল, কিন্তু সে সেই এড্রেসটা পায়নি বা অন্য কোন সমস্যা হওয়ায় সে NULL রিটার্ন করেছে।

আরেকটা ব্যাপার দেখা যাক।

int *ptr;
scanf( "%d", ptr );

এই কোড রান করলে প্রোগ্রাম ক্র্যাশ করবে। সমস্যা কি এই কোডের? এতক্ষণ যা আলোচনা করা হল তাতে বুঝতে পারার কথা। এখানে ptr নামে একটা পয়েন্টার ভেরিয়েবল ডিক্লেয়ার করা হয়েছে। কিন্তু ptr এর মধ্যে কোন int ভেরিয়েবলের এড্রেস নেই। তাই যখন আমি ptr এ ইনপুট নিতে চাচ্ছি তখন সে invalid মেমরি এক্সেস করতে যায়। ফলে প্রোগ্রাম ক্র্যাশ করে। এইজন্য যদি আমি পয়েন্টার ডিক্লেয়ার করে সেটা নিয়ে কাজ করতে চাই তাহলে তার জন্য মেমরি এলোকেট করে নিতে হয় malloc( ) দিয়ে।

int *ptr;
ptr = ( int * ) malloc( sizeof( int ) );
scanf( "%d", ptr );

দেখা যাক কি হচ্ছে। প্রথমেই আমরা একটা পয়েন্টার ডিক্লেয়ার করলাম ptr যেখানে কোন একটা মেমরি এড্রেস থাকবে যেই মেমরিতে শুধু মাত্র int থাকতে পারবে। এখানে আমরা কেবল এড্রেস রাখার একটা ভেরিয়েবল তৈরি করেছি। এখনও কিন্তু কোন মেমরি তৈরি হয়নি যেখান কোন একটা int আমরা রাখতে পারব। একারণেই আগের প্রোগ্রামটা ক্র্যাশ করেছিল। এবার আমরা দ্বিতীয় লাইনে ইন্টিজারের জন্য মেমরি এলোকেট করলাম। malloc( ) stdlib.h হেডার ফাইলের অন্তর্গত একটা ফাংশন। এটা কিভাবে কাজ করে দেখা যাক।

malloc( ) এর আর্গুমেন্ট হিসেবে কতখানি মেমরি আমাদের লাগবে সেটা লিখে দিতে হয়। এটা বাইটে হিসাব হয়। যদি আমরা লেখি malloc( 5 ) তাহলে ৫ বাইট মেমরি অর্থাৎ ৪০ বিট মেমরি এলোকেট হবে আমাদের জন্য। যেহেতু আমরা int রাখতে চাই তাই int এর সাইজ দিব। একেক কম্পিউটারে int এর সাইজ একেকরকম বলে আমরা sizeof( ) (এটা কোন ফাংশন না, এটা অপারেটর। এটুকু আপাতত জানলেই হবে যে এটা কিছু একটার সাইজ বলে দেয়।) ব্যবহার করব। ফলে যেই কম্পিউটারে চালানো হবে সেখানে int এর যে সাইজ সেই সাইজের মেমরি বরাদ্দ হবে।

এই যে মেমরি বরাদ্দ হল এটার তো কোন নাম আমরা দেইনি। তাহলে আমরা এই মেমরি ব্যবহার করব কিভাবে? এটা ব্যবহার করার উপায় হল মেমরি এর এড্রেস ব্যবহার করা। malloc( ) ফাংশন মেমরি বরাদ্দ করে সেই মেমরি এর শুরু যেখান থেকে সেই ব্লকের এড্রেস রিটার্ন করে। তাহলে আমরা যদি একটা int পয়েন্টারে সেই এড্রেসটা রাখি তাহলে সেই পয়েন্টার ব্যবহার করলেই হয়ে যাবে। যদি আমরা ptr = ( int * ) malloc( sizeof( int ) ) না লিখে লিখতাম, malloc( sizeof( int ) ) তাহলেও কিন্তু প্রোগ্রাম কাজ করবে এবং মেমরি বরাদ্দ হবে। কিন্তু আমাদের কাছে সেই মেমরি এর এড্রেস থাকবে না। ফলে আমরা মেমরি বরাদ্দ করব ঠিকই কিন্তু ব্যবহার করতে পারব না।

আরেকটা জিনিস এখানে দেখা যাচ্ছে, ( int * ) । ইতোমধ্যে জানা থাকার কথা এটা হল টাইপকাস্টিং। এক টাইপের ডাটা অন্য টাইপে রূপান্তর করা হচ্ছে। এর কারণ কি? malloc( ) যে পয়েন্টার রিটার্ন করে সেটার টাইপ থাকে void * । অর্থাৎ আমরা যেমন বললাম int *, char * ইত্যাদি বিভিন্ন পয়েন্টার থাকতে পারে, তেমনি একটা পয়েন্টার হল void * । এর মানে হল এটা কোন নির্দিষ্ট টাইপের পয়েন্টার না। কম্পিউটার জানে না এটায় যে এড্রেস থাকবে সেটি কিসের এড্রেস, অর্থাৎ সেখানে কি রাখা যাবে। malloc( ) void * রিটার্ন করে কারণ সে জানে না আমরা এই এড্রেসে কি রাখব। void * কে ব্যবহার করার জন্য টাইপকাস্ট করে নিতে হয়। যেই টাইপের ডাটা আমরা রাখতে চাই সেই টাইপে কাস্ট করব। আমরা যেহেতু একটা int * এর মধ্যে সেই এড্রেসটাকে রাখছি কারণ আমরা এই মেমরি ব্যবহার করব int রাখার জন্য, তাই এটাকে int * এ রূপান্তর করে নিচ্ছি ( int * ) এই টাইপকাস্টিং এর মাধ্যমে।

অর্থাৎ, উপরের কোডে যেটা হয় তা হল, প্রথমে ptr নামে একটা ভেরিয়েবল তৈরি হল যার মধ্যে একটা int এর জন্য বরাদ্দকৃত মেমরির এড্রেস থাকতে পারবে। এরপর আমরা malloc( ) দিয়ে int এর সাইজের মেমরি বরাদ্দ করলাম এবং যেখানে মেমরি বরাদ্দ করা হল সেই এড্রেসটা, খেয়াল কর, বরাদ্দকৃত মেমরির এড্রেসকে আমরা ptr এর মধ্যে রাখলাম। আবারও বলি, ptr কিন্তু সেই বরাদ্দকৃত মেমরির এড্রেস না। ptr একটা ভেরিয়েবল যার মধ্যে বরাদ্দকৃত মেমরির এড্রেস আমরা রাখলাম। এরপর সেই এড্রেসে আমরা scanf() দিয়ে ইনপুট নিলাম। malloc( ) যখন মেমরি এলোকেট করতে যায়, সে যদি যতখানি মেমরি এলোকেট করার কথা তা করতে না পারে, যেকোন কারণেই হোক না সেটা, যেমন যদি মেমরি না থাকে, অন্য কোন প্রোগ্রামের কারণে বা যেকোন কারণেই মেমরি ফুল হয়ে যাওয়ায় আর মেমরি না পেলে NULL রিটার্ন করে।

আরেকটা ফাংশন আছে free( )। এই ফাংশন দিয়ে আমরা এলোকেট করা মেমরি ফ্রি করে দিতে পারি। আমরা যখন একটা প্রোগ্রাম চালানোর সময় মেমরি এলোকেট করি তখন সেই মেমরি আর কোন প্রোগ্রাম ব্যবহার করতে পারে না। তাই আমাদের প্রোগ্রাম চালানোর সময় যখন মেমরি ব্যবহার করা শেষ হয়ে যাবে তখন আমরা সেই মেমরি ফ্রি করে দিব। এইজন্য আমরা যেই মেমরি ফ্রি করতে চাই সেই মেমরির এড্রেস আর্গুমেন্ট হিসেবে পাঠালেই তা ফ্রি হয়ে যাবে। যেমন, free( ptr ) বা free( &value ) ।

এই হল মূলত পয়েন্টার নিয়ে আলোচনা। আরেকটা ব্যাপার আছে, অ্যারের সাথে পয়েন্টারের সম্পর্ক। অল্প কথায় বলে ফেলি। যখন আমরা ডিক্লেয়ার করি, int ara[100]; তখন একটা ইন্টিজার অ্যারে তৈরি হয়। এই অ্যারের একেকটা এলিমেন্ট হল ara[0], ara[1], ara[2], … , ara[99] । আর “ara” হল একটা পয়েন্টার ভেরিয়েবল। এই অ্যারেটা মেমরিতে যেখান থেকে শুরু হয়েছে সেখানের এড্রেস আছে “ara” এর মধ্যে। খেয়াল করার বিষয় এইটা যে এই অ্যারেটির প্রথম ভেরিয়েবল হল ara[0] আর প্রথম ভেরিয়েবলের এড্রেস আছে ara এর মধ্যে। যদি আমরা ara+1 লেখি তাহলে যে এড্রেস পাব তা হল ara[1] এর এড্রেস। অতএব ara[1] হল দ্বিতীয় ভেরিয়েবল আর তার এড্রেস হল ara+1 । এড্রেস দিয়ে দ্বিতীয় ভেরিয়েবলটিকে ব্যবহার করতে চাইলে তাই লিখব *( ara + 1 ) । অর্থাৎ এই ara[index] এবং *( ara + index ) দুটি আসলে একই জিনিস, একই ভেরিয়েবলকে নির্দেশ করে। আবার &ara[index] এবং ( ara + index ) এই দুইটাও একই জিনিস, একই ভেরিয়েবলের এড্রেস নির্দেশ করে।

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