رفتن به مطلب
مرجع رسمی سی‌پلاس‌پلاس ایران

قاسم رمضانی منش

مدیران مرجع
  • تعداد ارسال ها

    97
  • تاریخ عضویت

  • روز های برد

    25

تمامی مطالب نوشته شده توسط قاسم رمضانی منش

  1. درود بر دوستان عزیز؛ برای اضافه کردن یک کاراکتر به انتهای رشته شاید افراد به این‌صورت عمل کنند : string[ strlen(string) ] = char; امّا من خواستم بدون وابسته بودن این قسمت به libc؛ این عمل انجام شود به این‌صورت عمل کردم : int main (void){ /* Define variable */ char c = 'm'; char* string = NULL; /* Allocating memory */ string = malloc(7); /* Copy char into `string` */ strcpy(string, (char*)"ghase"); /* Append `c` to end of `string` */ *((char*)(&string + 1) - 2) = c; *((char*)(&string + 1) - 1) = '\0'; /* Result */ printf("%s\n", string); return EXIT_SUCCESS; } امّا با سیگنال SIGSEGV در تابع __strlen_sse2() هنگام فراخوانی printf() مواجه می‌شوم، کلاً حافظهٔ دریافتی از طریق Derefrence کردن string مقادیرش قابل دسترسی نیست (پیغامی که GDB نشان می‌دهد). ولی همچنان می‌توانم به‌اینصورت به مقداردهی که کرده‌ام دسترسی داشته‌باشم : printf("%c\n", *((char*)(&string + 1) - 2)); الآن چه اتفاقی برای حافظهٔ متغیر string می‌افتد ؟
  2. درود و خسته‌نباشید به دوستان؛ می‌خواهم حالتی را مانند RadioButton که فقط می‌توان «یکی‌ از چندتا» را انتخاب کرد را در دکمه‌هایی که خودم نوشته‌ام پیاده کنم، در حالت عادی به این‌صورت عمل کرده‌ام و خب مشخصاً راه‌حل خوبی نیست استفاده از تابع ()fundOutAndTurnOnButtonLight؛ به چه روشی می‌توان بهتر عمل کرد ؟ main.qml ApplicationWindow { id: root; visible: true width: 640; height: 480 color: "gray" function findOutAndTurnOnButtonLight(id){ switch(id){ case pageOne: pageTwo.isSelected = false pageThree.isSelected = false break case pageTwo: pageOne.isSelected = false pageThree.isSelected = false break case pageThree: pageOne.isSelected = false pageTwo.isSelected = false break } id.isSelected = true } Row{ id: kepper anchors.centerIn: parent spacing: 20 MyRec{ id: pageOne onButtonClicked: findOutAndTurnOnButtonLight(pageOne) } MyRec{ id: pageTwo onButtonClicked: findOutAndTurnOnButtonLight(pageTwo) } MyRec{ id: pageThree onButtonClicked: findOutAndTurnOnButtonLight(pageThree) } } } و کامپوننت MyRec که در فایل MyRec.qml می‌باشد : Item { id: root width: 100; height: 100 property bool isSelected : false signal buttonClicked() Rectangle{ id: myButton anchors.fill: parent color: "red" width: parent.width / 4 height: parent.height / 4 } Rectangle{ id: bar anchors{ bottom: myButton.bottom horizontalCenter: myButton.horizontalCenter } color: "blue" width: myButton.width / 2 height: 5 visible: isSelected } MouseArea{ id: clickableArea anchors.fill: myButton onClicked: buttonClicked() } states:[ State{ name: "entered" when: clickableArea.pressed PropertyChanges{ target: myButton color: "yellow" } } ] }
  3. قاسم رمضانی منش

    درود و خسته‌نباشید به دوستان؛ در مستندات RadioButtonStyle مثالی به این‌صورت زده شده : RadioButton { text: "Radio Button" style: RadioButtonStyle { indicator: Rectangle { implicitWidth: 16 implicitHeight: 16 ... } ... } ... } امّا RadioButton خاصیّتی تحت عنوان style ندارد، آیا این مثال اشتباه است ؟ و چگونه می‌توان از RadioButtonStyle استفاده کرد ؟ این مورد دربارهٔ CheckBoxStyle نیز صدق می‌کند. ویرایش: باتوجه به مستندات : Import Statement: import QtQuick.Controls.Styles 1.4 Since: Qt 5.1 قابلیّت style در ویرایش 1.4 از ماژول QtQuick.Controls.Styles موجود می‌باشد، و پس از import کردن آن خطا رفع می‌شود. آیا اضافه کردن نسخهٔ 1.4 و 2.13 تداخلی به وجود می‌آورد ؟
  4. قاسم رمضانی منش

    درود و خسته‌نباشید به دوستان؛ درحال طراحی یک رابط‌کابری‌ساده بوده‌ام که خواستم قسمت رنگ‌آمیزی Itemها و Fontها و ... به راحتی قابل تغییر و برنامه‌ریزی باشد. اینکار را با استفاده از یک فایل QML جدا به اسم Style.qml به شکل زیر انجام دادم : pragma Singleton import QtQuick 2.13 Item { property int textinputTextSize : 22 property color transparent : "transparent" property color bluredColor : "#5AAFAAAA" property int tabBarWidth : 50 property int tabBarHeight : 75 property int tabBarIconSize : 44 property int tabBarTextSize : 20 property color tabBarIconColor: "#81D8DE" property color tabBarTextColor: "#059EAB" property color tabBarBackColor: "#0571AB" property alias sahelRegular : sahel_font.name property alias fontAwesome : font_awesome.name FontLoader{ id: sahel_font source: "qrc:/assets/fonts/sahel/Sahel-FD-WOL.ttf" } FontLoader{ id: font_awesome source: "qrc:/assets/fonts/awesome/fontawesome-regular.ttf" } property color mainpageColor : "#E3F2FD" property color mainpageToolbarColor : bluredColor } آیا این روش بهینه و درست است ؟ و یا راه بهتری هم وجود دارد ؟
  5. قاسم رمضانی منش

    کتابخانه‌های استاتیک و داینامیک (پویا)

    توضیحات موردنیاز، قبلاً در این‌پیوند داده شده. حال بیاید ببینیم در عمل چگونه‌است ؟ Static Library یا کتابخانه‌های استاتیک : معمولاً تحت عنوان Archives هم شناخته می‌شوند، یک Static Library شامل مجموعه‌ای از Object-Fileها هست. Object-Fileها سورس‌های کامپایل‌ شدهٔ ما به زبان‌ماشین هستند. این فایل‌ها قابل اجرا نیستند چراکه هنوز کتابخانه‌های موردنیازشان Link نشده. برای کامپایل به‌صورت Object-File از فلگ -c استفاده می‌کنیم : $> cc -c func.c $> cc -c main.c $> cc *.o -o output در اینجا ما سورس‌کدهای func.c و main.c را فقط کامپایل کردیم و بعد (در خط سوّم) Object-Fileها را به کامپایلرمان دادیم تا عمل لینک کردن کتابخانه‌ها و خروجی‌نهایی را تولید کند. برای ساخت Static Library ما از Object-File‌ها به همراه برنامهٔ ar استفاده می‌کنیم، به این‌صورت که اوّل Object-Fileها را تولید می‌کنیم : $> cc -c func1.c $> cc -c func2.c و حالا یک کتابخانه متشکل از Object-Fileها برای ساخت Static-Libraryمان خروجی می‌گیریم : $> ar rcs libfunc.a func1.o func1.o خب ! در این قسمت دو نکتهٔ کوچک و مهم وجود دارد : فایل‌هایی که با استفاده از فلگ -c کامپایل می‌کنید، خروجی‌حاصل فایلی با همان نام فایل ورودی به همراه پسوند .o می‌باشد. اسم کتابخانهٔ شما باید به‌صورت lib*.(a|os) باشد. و این‌چیزی هست که Linker به دنبال آن برای لینک‌کردن می‌گردد. برای کتابخانه‌های‌استاتیک ما از پسوند .a استفاده می‌کنیم و برای کتابخانه‌های‌داینامیک از .so . حال برای استفاده از این کتابخانه‌ما نیاز به دوکار کوچک داریم هنگام کامپایل نهایی داریم : $> cc main.c -L. -lfunc -o output فلگ -L برای مشخص کردن دایرکتوری‌ای که کتابخانهٔ ما در آن قرار دارد استفاده می‌شود. (میدانیم که در UNIX هر دایرکتوری دارای دو لینک می‌باشد؛ یک . (dot) که اشاره به دایرکتوری جاری دارد و.. (dot-dot) که اشاره به دایرکتوری-پدر (parent-directory یا دایرکتوری بالایی دارد). فلگ -l برای مشخص کردن اسم کتابخانهٔ ما استفاده می‌شود، دیدید که ما فقط اسم func را آوردیم، چرا که خود تصور می‌کند اوّل اسم فایل lib و پسوند آن .a یا .so می‌باشد. یک نمونهٔ عملی را می‌توانید از این‌قسمت امتحان کنید : در این مثال از GNU Make استفاده شده است، درصورتی‌که آشنایی ندارید می‌توانید از این‌قسمت با GNU Make آشنا بشوید. امّا نکته‌ای که قابل ذکر هست : در این‌جا شما فقط کتابخانه‌ای که خودتان نوشتید را به‌صورت Static لینک کردید، کتابخانه‌هایی مثل glibc به‌صورت خودکار درحالت Dynamic لینک می‌شوند. Shared Library یا کتابخانه‌ داینامیک : در این روش بازهم ما نیاز به Object-Fileهای سورس‌کد‌ها داریم، با تفاوت اینکه باید فلگ -fPIC یا -fpic را اضافه کنیم که به معنی Position-independent Code می‌باشد؛ می‌دانید که Shared Libraryها یک‌بار فقط در حافظه بارگذاری می‌شوند از این رو نیاز است که سورس‌کدماشینی که تولید می‌شوند وابسته به این نباشد که در جای به خصوصی از حافظه بارگذاری شود. خب Object-Fileها را به صورت PIC کامپایل می‌کنیم : $> cc -c -fPIC add.c $> cc -c -fPIC sub.c حال باید کتابخانهٔ‌اشتراکی خود را با استفاده از فلگ -shared ایجاد کنیم : $> cc -shared add.o sub.o -o libmat.so در اینجا ما از فلگ -shared استفاده کردیم و Object-Fileهای تولیدشده را به عنوان ورودی وارد کرده‌ایم. و حالا می‌توانیم از shared library خودمان استفاده کنیم : $> cc main.c -o output -L. -lmath حال بیاید برنامه را اجرا کنیم : $> ./output ./output: error while loading shared libraries: libmat.so: cannot open shared object file: No such file or directory چرا ؟ به خاطر اینکه linker در آدرس‌های تعریف شده به دنبال کتابخانهٔ‌اشتراکی libmat.so می‌گردد. راه‌های مختلفی برای مشخص کردن مسیر کتابخانهٔ خودمان وجود دارد. انتقال کتابخانهٔ خود به آدرس /usr/lib دستکاری LD_LIBRARY_PATH ... راه‌های مختلف را می‌توانید از لینک‌های زیر دنبال کنید : https://renenyffenegger.ch/notes/development/languages/C-C-plus-plus/GCC/create-libraries/index https://www.cprogramming.com/tutorial/shared-libraries-linux-gcc.html موفق‌وپیروز باشید ?.
  6. قاسم رمضانی منش

    درود بر شما؛ برای اینکار می‌توانید شما از QScrollArea استفاده کنید. این کلاس یک منطقهٔ‌نمایش Widgetها و یک QScrollBar را به شما می‌دهد، و شئ را که می‌خواهید Scroll کنید را باید به عنوان Child Widget به آن معرفی کنید. برای مثال : #include <QApplication> #include <QDialog> #include <QGroupBox> #include <QList> #include <QScrollArea> #include <QTextEdit> #include <QVBoxLayout> int main(int argc, char** argv) { QApplication application(argc, argv); QDialog dialog; QVBoxLayout layout; QScrollArea scroll(&dialog); QGroupBox groupedArea; QList<QTextEdit*> widgets; for (int i {}; i < 20; ++i) { widgets.append(new QTextEdit); layout.addWidget(widgets.at(i)); } scroll.setWidget(&groupedArea); scroll.setWidgetResizable(true); groupedArea.setLayout(&layout); dialog.show(); return application.exec(); } دقّت کنید که باید حتماً تابع setWidgetResizable را با مقدار true برای تغییر اندازهٔ مناسب Widget فراخوانی کنید. خروجی حاصل از کد بالا : با تشکر از آقای‌رضوی.
  7. قاسم رمضانی منش

    @nabegheh95 درود بر شما؛ لازم به ذکر بود که، برنامهٔ Microsoft Visual Studio یک IDE توسعه داده شده توسط شرکت Microsoft می‌باشد. و برای توسعهٔ هرچه بهتر برنامه‌های سیستم‌عامل Windows تحت Microsoft .Net می‌باشد. درواقع شما با کامپایلر Microsoft Visual C++.Net کارکردید. برای فریمورک Qt هم یک IDE بسیارعالی وجود دارد که سرعت توسعهٔ نرم‌افزار با استفاده از این فریمورک را افزایش می‌دهد، پس پیشنهاد می‌کنم به جای استفاده از آن افزونه مستقیم از این IDE استفاده کنید چرا که یک IDE بسیار خوب نیز برای ++C\C می‌باشد. تنها پیشنهاد می‌شود که در سیستم‌عامل Microsoft Windows از کامپایلر Microsoft Visual C++.Net استفاده کنید، این کامپایلر برای سیستم‌عامل خود به خوبی عمل می‌کند. هرچند که باید حواستان به تنبلی‌های مایکروسافت باشد مواردی مثل مورد زیر اصلاً پیاده‌سازی نشده‌اند: void char_exception() throw(int){ throw 'x'; } int main(void){ try{ char_exception(); } catch (...){ std::cout << "Caught it." << std::endl; } return 0; } توضیح. فریمورک Qt یک مجموعهٔ عظیم از کتابخانه‌ها می‌باشد، که تماماً نیز در این‌سایت مستند شده‌اند. برای رفع ابهامات کافی است که مستندات را به دقّت مطالعه و نمونه‌های نوشته شده را بررسی و امتحان کنید. اجرا شدن یک فایل، بسته به کرنل یک سیستم‌عامل و نحوهٔ پیاده‌سازی آن می‌باشد. پس مسلماً ساختاری که در کرنل Windows NT به عنوان فایل اجرایی شناخته شده است، در کرنل Linux قابل قبول نیست. برای اطلاعات بیشتر درمورد این ساختارها می‌توانید ساختار PE و ELF را بررسی کنید. در حالت ایده‌آل، بلی باید تلاش داشته‌باشید که از APIهایی که Qt فراهم می‌کند استفاده کنید، امّا همیشه این کار شدنی نیست. شما همیشه نیازی به استفاده از Qt‌ ندارید و ممکن است قابلیّتی در یک سیستم‌عامل نیاز داشته‌باشید که مشابه آن در یک سیستم‌عامل دیگر متفاوت هست. برای مثال : این سورس Telegram/ThirdParty/minizip/ioapi.c هست، ببینید به چه صورت در خط‌های ۱۳ و ۱۷ و ۲۲ بسته به سیستم‌عامل مقصد مشخص کرده‌است که چه توابعی باید کامپایل شوند. با این روش شما درحالی که از قابلیّت خاص یک سیستم‌عامل استفاده کردید ولی برنامهٔ شما Cross-Platform می‌باشد. پیشنهاد می‌کنم کتاب «++API Design For C» نوشتهٔ آقای «Martin Reddy» را مطالعه کنید.
  8. قاسم رمضانی منش

    @Alireza4 درود؛ اینکه چه کاری انجام بدهید و به کدام راه برید، تماماً بستگی به خودتان دارد. پیشنهاد می‌کنم که قشنگ درمورد کاری که می‌خواهید انجام بدید تحقیق کنید : - هدفتان از برنامه‌نویسی چیست ؟ - چقدر حوصلهٔ یادگیری مطالب را دارید ؟ - چقد دید سیستمی به برنامه‌نویسی دارید ؟ و ..، این مطلب را حتماً مطالعه کنید : کدام زبان برنامه‌نویسی را انتخاب کنم ؟ ، در این مقاله توضیحات لازم داده شده که بتونید خودتان تصمیم بر انتخاب زبان بگیرید. چرا که باید بدونید «درسته‌که زبان سی‌پلاس‌پلاس قدرت زیادی داره» امّا باید بدونید که هرچیز خوبی بالاخره هزینه‌ای هم داره و یادیگری کار کردن با این زبان به شش‌ماه تموم نمیشه، برای اینکه دراین‌باره هم بیشتر اطلاعات کسب کنید مقالهٔ چرا و چگونه باید ++C را یادبگیریم ؟ ، شاید اصلاً چیزی نبود که شما فکر می‌کردید.
  9. قاسم رمضانی منش

    @MahdiGameMaker خطای «دوباره پاک کردم پکیج ها رو و بعد نصب کردن باز هم ارور میده»،‌ ارور خوبی نمی‌باشد که ما نیز راهنمایی‌ای برای آن بکنیم. لطفاً یک لاگ با اطلاعات کافی (از طریق یک سرویس paste) ارائه بدید.
  10. قاسم رمضانی منش

    سلام؛ خوش‌آمدید، لطفاً ده-دقیقه وقت بگذارید و اسناد زیر را جهت فعالیت در انجمن مطالعه بکنید : قوانین نگارشی جهت نشر محتوا سند نحوه‌ی پرسش و پاسخ هوشمندانه سیستم‌عامل گنو/لینوکس، یک سیستم‌عامل آزاد - متن‌باز می‌باشد، که می‌توانید به رایگان آن را دریافت و استفاده کنید : سیستم‌عامل گنو/لینوکس و جنبش نرم‌افزار آزاد آزاد به چه معنی است ؟ نسخه‌های متعدد و زیادی از سیستم‌عامل‌های گنو بر پایه کرنل لینوکس موجود می‌باشد که می‌توانید هرکدام را به رایگان دریافت و استفاده بکنید : دریافت سیستم‌عامل آزاد دبین. جهت شروع آشنایی کار با این سیستم‌عامل‌ها می‌توانید از این دورهٔ آموزشی آقای‌میر‌میرانی شروع کنید : دورهٔ آموزشی مدرک LPIC-1 نیازی به بارگیری SDK و NDK دوباره نیست، ولی برای JDK بسیار بهتر می‌باشد که از طریق مدیربستهٔ توزیعتان اقدام به نصب آن کنید که اختلالی پیش نیاید. مطمئن شوید که این سند را نیز مطالعه کردید : کیوت برای اندروید.
  11. قاسم رمضانی منش

    خواهش‌می‌کنم؛ راحت‌باشید درصورتی‌که اشتباهی یافتید اصلاح کنید.
  12. قاسم رمضانی منش

    استفاده از MD

    زبان نشانه‌گذاری Markdown یا به اختصار MD همانند زبان HTML برای طراحی قالب متن استفاده می‌شود. به گمانم که قبلاً نمونه‌های زیادی را تحت عنوان README.md شنیده‌باشید. مانند نمونهٔ زیر : استفاده از این قالب بسیار ساده‌ است، حال بخشی از نحوهٔ نگارش فایل‌های MD را می‌گوییم. برای نوشتن سر‌نویس‌ها (Header) : Header1 ======= # Header1 ## Header2 ### Header3 برای "خط‌جدید" (NewLine) از دو کاراکتر Space در انتهای هر پاراگراف استفاده می‌کنیم و یا از دو/n (توسط کلید ReturenKey) استفاده می‌کنیم : first line. second line. ویژگی‌های متن : _italic_ or *italic* __bold__ or **bold** `monospace` نوشن یک بلاک کد : ```<Language-Name> // And Write Code Here ``` ```c int main (int argc, char **argv, char **envp){ /* some code here */ return EXIT_SUCCESS; } ``` or indent your code by four space: int main (int argc, char **argv, char **envp){ /* some code here */ return EXIT_SUCCESS; } انواع لیست‌ها : Bullet List: * One Option * Another Option Numbered List: 1. Free Software 2. GNU/Linux Todo List: - [x] it's done. - [ ] working on it. نوشتن پیوند‌ها : [GNU/Linux](www.kernel.org) ![Image](https://en.wikipedia.org/wiki/File:Qt_logo_2016.svg) استفاده از حالت "نقل‌قول" : > for using blockquoting in md files. استفاده از جداول : Prorgamming Language | It's God ? ---------------------|------------ C | Very GOD C++ | It's God Python | :( خروجی تمام نمونه‌های بالا : استفاده از قالب‌های گفته شده، بستگی به پیاده‌سازی موتور رندر ویرایشگری دارد که شما از آن استفاده می‌کنید، چرا که ممکن است تمام قابلیت‌ها را پیاده‌سازی نکرده‌باشد. موفق‌وپیروز باشید. ?
  13. قاسم رمضانی منش

    عیب‌یابی، دیباگ‌کردن با GNU Debugger

    بارها بوده که برنامه‌‌ای را نوشته‌ایم، روند کامپایل و اجرا به خوبی و خوشی انجام می‌شود. امّا در مرحلهٔ اجرای برنامه، خروجی‌های نامناسبی پدیدار می‌شود. که متأسفانه چیزی نیستند که ما می‌خواهیم. خب برای حل این مشکل دو راه پیش‌رو می‌باشد : بازبینی کد و انجام تست برای یافتن محل مشکل. استفاده از ابزارهای خطایابی (Debugging). بازبینی کد و انجام تست برای یافتن محل مشکل برای این‌کار فریورک‌ها و کتابخانه‌های زیادی موجود می‌باشد، مثلاً کتابخانهٔ تست‌نویسی (Test Case) به اسم Catch2. کار با این کتاخانه بسیار آسان است. برای مثال تابع زیر را در نظر داشته‌باشید : unsigned int Factorial( unsigned int number ) { return number <= 1 ? number : Factorial(number-1)*number; } ما می‌خواهیم بدانیم که آیا این تابع خروجی مناسب را دارد یا خیر، درصورتی‌که از catch2 استفاده می‌کنید، کافیست که طبق راهنمای README.md هدرفایل catch.cpp را دریافت و به برنامهٔ خود اضافه کنید : #define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file #include "catch.hpp" unsigned int Factorial( unsigned int number ) { return number <= 1 ? number : Factorial(number-1)*number; } TEST_CASE( "Factorials are computed", "[factorial]" ) { REQUIRE( Factorial(1) == 1 ); REQUIRE( Factorial(2) == 2 ); REQUIRE( Factorial(3) == 6 ); REQUIRE( Factorial(10) == 3628800 ); } و بهصورت نمونهٔ کد بالا از catch2 استفاده می‌کنیم. طبق اسناد با تعریف ماکروی CATCH_CONFIG_MAIN ما یک تابع main توسط خود catch2 تعریف می‌کنیم. و کافیه که فقط این سورس را کامپایل و اجرا کنیم : $> g++ -o catch2Test main.cpp $> $> ./catch2Test =============================================================================== All tests passed (4 assertions in 1 test case) و خب مسلماً درصورتی‌که خطایی باشد در این آزمایشات معلوم می‌گردد،‌ فریمورک‌های دیگری مانند Google Test نیز موجود می‌باشد. استفاده از ابزارهای خطایابی (Debugging) در این روش شما برنامهٔ خود را تحت برنامهٔ دیگری اجرا و اقدام به خطایابی می‌کنید، یکی از برنامه‌های خطایابی معروف و آزاد، برنامهٔ GNU Debugger می‌باشد. از این برنامه برای خطایابی در زبان‌های : Ada Assembly C ++C D Fortran Go Objective-C OpenCL Modula-2 Pascal Rust استفاده می‌شود. برای نصب می‌توانید از مدیربستهٔ سیستم‌عامل خود استفاده کنید : $> apt install gdb gdb-doc و یا اینکه از این پیوند برنامه را دریافت و اقدام به کامپایل/نصب آن کنید. نکته : دقت کنید که همیشه نباید حتماً خطایی در برنامه باشد تا اقدام به خطایابی کنیم، گاهی هم نیاز است که روند کار برنامه را به این روش با استفاده از ابزارهای مشابه پی‌گیری کنیم. (In fact: reverse engineering) حال اقدام به Debug کردن این برنامهٔ کوتاه می‌نماییم : #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> static int data = 0x02A; int main (void){ int stack = 0x2FF; pid_t pid; switch (pid = fork()){ case -1: perror("pid = fork()"); exit(EXIT_FAILURE); case 0: data |= 0x02B; stack &= data; break; } fprintf(stdout, "[%s]\v [PID=%ld] [data=%d] [stack=%d].\n", (pid == 0) ? "child" : "parent", (long) getpid(), data, stack); exit(EXIT_SUCCESS); } قبل‌از اینکه برنامه‌ای را برای دیباگ‌ کردن داخل GDB استفاده کنیم،‌ نیاز است که برای کمک به این روند اطلاعاتی مانند مکان Source Code برنامه را به فایل باینری خود اضافه کنیم. برای اینکار کافیست که از فلگ -g یا -ggdb یا -g3 استفاده کنیم. این پیوند را برای اطلاعات بیشتر مطالعه کنید. به این‌صورت برنامه را کامپایل و برای دیباگ کردن آماده می‌کنیم : $> gcc -o output -ggdb main.c توجه کنید که سورس برنامه را از مکانش تغییر ندهید. حال برنامهٔ gdb را با نوشتن اسم gdb در shell فراخوانی می‌کنیم : $> gdb GNU gdb (Debian 8.2.1-2) 8.2.1 Copyright (C) 2018 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word". (gdb) برای اینکه فایل باینری را باز کنیم باید از دستور file استفاده کنیم. همچنین می‌توانیم به این‌صورت نیز برنامهٔ gdb را به همراه فایل باینری خود اجرا نماییم : $> gdb output خب،‌ در این قسمت می‌توانیم با استفاده از دستور run برنامهٔ خودمان را اجرا کنیم و درصورتی‌که نیاز باشد آرگومان‌هایی را نیز ارسال کنیم. وقتی دستور run‌ را وارد می‌کنیم برنامهٔ ما اجرا می‌شود و خاتمه میابد. زمانی‌که نیاز داریم روند اجرای برنامه در نقطه‌های مشخصی متوقف شود باید از ‌Break Point استفاده کنیم. برای قرار دادن Break Point در خط‌های مختلف برنامه از دستور break به اضافهٔ شمارهٔ خط سورس برنامه استفاده می‌کنیم. برای اینکار بد نیست که اطلاعاتی نیز درمورد سورس‌کد خود داشته‌باشیم مثلاً همزمان بتوانیم سورس را نیز مشاهده کنیم، درصورتی‌که از ادیتور Emacs استفاده می‌کنید می‌توانید M+x را زده و gdb را اجرا کنید. که یک بافر در سمت چپ برای شما باز می‌کند (درصورتی‌که دکمه‌های Ctrl + X و Ctrl + 3 را زده باشید). در غیر این‌صورت به دو روش دیگر می‌توانید سورس خود را هنگام دیباگ مشاهده کنید، در روش اوّل استفاده از دستور list به اضافهٔ دو آرگومان می‌باشد : آرگومان اوّل مشخص کنندهٔ خط شروع است، و آرگومان دوّم مشخص کننده خط پایان هست. مثلاً با فراخوانی list 2, 10 از خط دو تا ده را نمایش می‌دهد : (gdb) list 2, 10 2 #include <stdlib.h> 3 #include <sys/types.h> 4 #include <unistd.h> 5 6 static int data = 0x02A; 7 8 int 9 main (void){ 10 int stack = 0x2FF; می‌توانید فقط یک آرگومان ارسال کنید، که به اندازه ی مقدار متغیر listsize که پیشفرض ده می‌باشد (می‌توانید با استفاده از دستور set مقدار را تغییر دهید) را نمایش می‌دهد : (gdb) list 2 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <sys/types.h> 4 #include <unistd.h> 5 6 static int data = 0x02A; 7 8 int 9 main (void){ 10 int stack = 0x2FF; حالات دیگه‌ای هم دارد که می‌توانید با وارد کردن help list متوجه شوید. امّا روش بهتری (روش دوّم) نیز برای دیدن سورس برنامه به همراه دیباگ کردن وجود دارد،‌ می‌توانید از رابط TUI استفاده کنید. برای استفاده از این رابط دکمه‌های Ctrl + X و Ctrl + A یا < وارد کرده و ReturnKey (یا همان Enter) را بزنید. در این قسمت شما به راحتی می‌توانید هم سورس برنامهٔ خودتان را ببینید و هم برنامه را دیباگ کنید. با یک‌بار اجرا کردن برنامه توسط دستور run سورس برنامه‌ٔ شما بارگذاری می‌شود. برویم به سراغ Break Point گذاشتن، برای مثال ما می‌خواهیم یک Break Point بر سر خط ۱۰ و ۱۳ بگذاریم : (gdb) break 11 Breakpoint 1 at 0x555555555185: file main.c, line 13. (gdb) break 10 Breakpoint 2 at 0x55555555517e: file main.c, line 10. (gdb) دوباره برنامه را با وارد کردن دستور run اجرا می‌کنیم، تا اجرای برنامه این‌بار در برخورد با اوّلین Break Point متوقف شود : در این توقف، ما می‌توانیم با استفاده از دستور print محتویات متغیرهارا مشاهده کنیم : (gdb) print data $1 = 42 (gdb) print stack $2 = 21845 دستور print قابلیت‌های جالبی دارد : (gdb) print 12 / 2 $1 = 6 (gdb) print sizeof(int) $2 = 4 (gdb) print &data $3 = (int *) 0x4a60f0 <data> (gdb) مقادیری که به ترتیب در سمت چپ شماره‌گذاری شده‌اند درواقع اسم متغیرهایی هستنند که خروجی در آن قرار گرفته است : (gdb) print $1 $4 = 42 (gdb) print $4 $5 = 42 (gdb) همچنین با استفاده از دستور delete می‌توانیم یک Break Point را حذف کنیم. برای ادامه دادن به روند اجرای برنامه تا Break Point بعدی از دستور continue و برای رفتن به خط بعدی از دستور next استفاده می‌کنیم.بعد از اجرای دستور next دقت کنید سریعاً به خط 23 رفته و فراخوانی تابع سیستمی fork() را رها می‌کند. به خاطر اینکه دستور next کاری به توابعی که فراخوانی کرده‌اید، در اینجا فراخوان سیستمی fork() ، ندارد و دستورات سورس شما را ادامه می‌دهد؛ امّا درصورتی‌که از step یا stepi استفاده بکنید وارد دستورات شده و دستورات توابع شما را پیمایش می‌کند: (gdb) next [child] [PID=563] [data=43] [stack=43]. [Detaching after fork from child process 563] (gdb) نکته : رابط TUI زیاد قوی نمی‌باشد،‌ لذا درصورتی‌که خروجی چاپ شود تنظیمات صفحه نمایش را به هم می‌زد می‌توانید با زدن Ctrl + L خروجی‌های اضافه را از بین ببرید. برای دیباگ کردن یک fork() می‌توانید مقدار follow-fork-mode را ویرایش کنید : (gdb) set follow-fork-mode child (gdb) run Starting program: /tmp/output Breakpoint 2, main () at main.c:10 (gdb) next Breakpoint 1, main () at main.c:13 (gdb) step [Attaching after process 2387 fork to child process 2403] [New inferior 2 (process 2403)] [Detaching after fork from parent process 2387] [Inferior 1 (process 2387) detached] [Switching to process 2403] main () at main.c:13 (gdb) همچنین با استفاده از دستور disassemble می‌توانید سورس اسمبلی یکی تابع را مشاهده کنید : (gdb) disassmble main Dump of assembler code for function main: 0x0000000000401b4d <+0>: push %rbp 0x0000000000401b4e <+1>: mov %rsp,%rbp 0x0000000000401b51 <+4>: push %rbx 0x0000000000401b52 <+5>: sub $0x18,%rsp => 0x0000000000401b56 <+9>: movl $0x2ff,-0x14(%rbp) 0x0000000000401b5d <+16>: callq 0x43c9b0 <fork> 0x0000000000401b62 <+21>: mov %eax,-0x18(%rbp) 0x0000000000401b65 <+24>: cmpl $0xffffffff,-0x18(%rbp) 0x0000000000401b69 <+28>: je 0x401b73 <main+38> 0x0000000000401b6b <+30>: cmpl $0x0,-0x18(%rbp) 0x0000000000401b6f <+34>: je 0x401b89 <main+60> 0x0000000000401b71 <+36>: jmp 0x401ba2 <main+85> 0x0000000000401b73 <+38>: lea 0x7c48e(%rip),%rdi # 0x47e008 0x0000000000401b7a <+45>: callq 0x408bd0 <perror> 0x0000000000401b7f <+50>: mov $0x1,%edi 0x0000000000401b84 <+55>: callq 0x408010 <exit> 0x0000000000401b89 <+60>: mov 0xa4561(%rip),%eax # 0x4a60f0 <da --Type <RET> for more, q to quit, c to continue without paging-- لینک منبع را برای ادامهٔ داستان دنبال کنید :). موفق و پیروز باشید.?
  14. قاسم رمضانی منش

    @GornerLabo سلام؛ سلامت‌باشید، خواهش‌میکنم امیدوارم مفیدبوده باشه. برنامهٔ tar این قابلیت را به شما میده که بتونید بدون استخراج یک فایل، محتویات آن فایل را مشاهده کنید. امّا در هر صورت باید آن فایل را استخراج کنید. حالا اگه روشی برای اینکه یک فایل باینری را از داخل این آرشیوها استخراج و استفاده کنیم را نمی‌دانم. امّا برای اینکه محتویات یک فایل را مشاهده کنیم می‌توانیم از فلگ -O استفاده کنیم، به این‌صورت مثلاً فایلی‌هایی با این محتویات داریم : . ├── AGE-id1 │ ├── contents-> 'AGE=18' ├── AGE-id2 │ ├── contents-> 'AGE=19' ├── NAME-id1 │ ├── contents-> 'NAME=ghasem' └── NAME-id2 ├── contents-> 'NAME=Javad' و آنها را آرشیو می‌کنیم : $> tar cf tmp.tar * و حال می‌خواهیم محتویات یکی از آنها را ببینیم، می‌توانیم از -O استفاده کنیم : $> tar xfO tmp.tar NAME-id1 NAME=ghasem $> شما شاید بتوانید جریان stdout را به یک فایل هدایت کنید، ولی درنهایت می‌شود همان استخراج کردن. درصورتی‌که راهی برای اینکار بود خوش‌حال می‌شویم به اشتراک بگذارید.
  15. قاسم رمضانی منش

    کتابخانهٔ Zlib

    چندی پیش یکی از دوستان درمورد کتابخانهٔ zlib از من سوأل پرسیده بود که جالب شد برایم تا نگاهی بکنم. zlib یک کتابخانهٔ فشرده‌سازی بر اساس الگوریتم DEFLATE هست که خود این الگوریتم تلفیقی از LZ77 و الگوریتم Huffman هست و عمل فشرده‌سازی‌درحافظه را انجام می‌دهد، اطلاعات بیشتر درمورد اینا خواستید از این‌جا استفاده کنید. این کتابخانه یک قسمت مهم از پلتفرم‌های معروفی همچون GNU/Linux , iOS و.. هست. تستی که با این کتابخانه انجام دادم واقعاً برایم جالب بود، یک فایل متنی ۶۰۰ مگابایتی را به ۱۲۱ مگابایت رسوند در مدّت زمان خیلی کوتاهی با یک پردازندهٔ Intel(R) Core(TM) i7 CPU M 620. خب بریم یک تست بکنیم. اوّل سورس برنامه را از این قسمت بارگیری کنید : https://www.zlib.net/ توضیحات مفصل را می‌خواید می‌توانید از این‌قسمت استفاده کنید. امّا من از یک‌مثال استفاده می‌کنم، بعد از اینکه سورس‌کد را دانلود کردید کافیه که از حالت فشرده خارجش کنید و وارد دایرکتوری مربوطه‌اش بشید. برای کامپایل شما نیاز به : GCC GNU Make دارید، اگر نمی‌دانید GNU Make چی هست، می‌توانید در این‌قسمت با GNU Make آشنا بشید. خب اگر ابتدا برنامهٔ make را داخل دایرکتوری فراخوانی کنید پیغام زیر را نمایش میدهد : Please use ./configure first. Thank you. که مؤدبانه خواهش می‌کند اوّل اسکریپت configure را اجرا کنیم، بعد از اجرای این اسکریپت چک‌های لازم انجام می‌شود و بعد می‌توانید make را اجرا کنید تا کتابخانه‌‌های مورد نظر ساخته بشود. بعد اتمام کار، ما فقط نیاز به Shared lib ها و Header File مربوطه داریم. (درصورتی‌که نمی‌دانید Shared Lib چیست، می‌توانید در این‌قسمت با نحوهٔ‌کار/ساخت آن آشنا شوید). پس بهتر است یک دایرکتوری به اسم lib در ساختار دایرکتوری پروژهٔ خودمان درست کنیم و به این‌صورت عمل کنیم : zlib-1.2.11$> mkdir ../zlibTEST/lib zlib-1.2.11$> mv libz*so* ../zlibTEST/lib renamed 'libz.so' -> '../zlibTEST/lib/libz.so' renamed 'libz.so.1' -> '../zlibTEST/lib/libz.so.1' renamed 'libz.so.1.2.11' -> '../zlibTEST/lib/libz.so.1.2.11' zlib-1.2.11$> mv zlib.h ../zlibTEST/header renamed 'zlib.h' -> '../zlibTEST/header/zlib.h' در این قسمت، اوّل ما خارج‌از دایرکتوری zlib داخل یک دایرکتوری دیگر که پروژهٔ ما درآن قرار دارد یک دایرکتوری به اسم lib ساختیم که shared lib و header file مربوطه را درآن قرار دهیم. سپس تمام فایل‌هایی که به اسم 'libz*so*' هستند را به آن دایرکتوری انتقال دادیم؛ سه فایل قرار دارد که دو تا از آنها به libz.so.1.2.11 لینک شده‌اند. خب بریم سروقت تست خودمان. اوّل از همه نیاز به هدرفایل‌های مربوطه داریم : #include <stdio.h> #include <string.h> #include <assert.h> #include "zlib.h" کتابخانهٔ zlib از ثابت CHUNK برای مقداردهی Buffer خودش استفاده می‌کنه، و ما نیاز داریم که این ثابت را تعریف کنیم : #define CHUNK 251904 هرچی مقدار بیشتر باشه سیستم کارآمد تر هست، داخل خود اسناد گفته که بهتره از 256k استفاده کنیم درصورتی‌که مقدار حافظهٔ موردنیاز رو داریم. حالا باید تابع Compressing خودمان را با استفاده از کتابخانهٔ zlib پیاده کنیم. ما اسم این تابع را compressing میزاریم، این تابع دو stream دریافت می‌کند که یکی ورودی و یکی خروجی می‌باشد. یک ورودی دیگر تابع سطح فشرده‌سازی هست که در ادامه بحث می‌کنیم : int compressing (FILE *source, FILE *dest, int level); خروجی‌ه تابع می‌تواند این موارد باشد : - Z_OK = 0 - Z_STREAM_END = 1 - Z_NEED_DICT = 2 - Z_ERRNO = -1 - Z_STREAM_ERROR = -2 - Z_DATA_ERROR = -3 - Z_MEM_ERROR = -4 - Z_BUF_ERROR = -5 - Z_VERSION_ERROR = -6 از اسامی تقریباً مشخص هست که چه مفهومی دارند و نیازی به توضیح نیست. حال نیاز هست که یک سری متغیر‌های‌محلی که فقط مورد استفادهٔ خود تابع هست را داخل تابع تعریف کنیم : int return_; int flush; int have; z_stream stream; unsigned char input[CHUNK]; unsigned char output[CHUNK]; متغیر اوّل که از اسم‌ش مشخص هست، برای مشخص کردن مقداربازگشتی از تابع هست، و متغیر دوّم برای مشخص کردن وضعیّت flushing برای یکی از توابع zlib هست. متغیر سوّم مقدار اطلاعاتی هست که از یکی از توابع zlib به اسم deflate() بر می‌گردد. متغیر چهارم هم از نوع یک ساختار داخلیه zlib می‌باشد : typedef struct z_stream_s { z_const Bytef *next_in; /* next input byte */ uInt avail_in; /* number of bytes available at next_in */ uLong total_in; /* total number of input bytes read so far */ Bytef *next_out; /* next output byte will go here */ uInt avail_out; /* remaining free space at next_out */ uLong total_out; /* total number of bytes output so far */ z_const char *msg; /* last error message, NULL if no error */ struct internal_state FAR *state; /* not visible by applications */ alloc_func zalloc; /* used to allocate the internal state */ free_func zfree; /* used to free the internal state */ voidpf opaque; /* private data object passed to zalloc and zfree */ int data_type; /* best guess about the data type: binary or text for deflate, or the decoding state for inflate */ uLong adler; /* Adler-32 or CRC-32 value of the uncompressed data */ uLong reserved; /* reserved for future use */ } z_stream; توضیحات‌َش داده شده داخل خودzlib.h که این ساختار به چه شکلی هست و هر مقدار برای چه کاری هست. و دو متغیر بعدی بافرهای ورودی و خروجی‌ما می‌باشد. کتابخانهٔ zlib از روش تخصیص‌حافظهٔ به خصوص خود استفاده می‌کند، از این رو باید ساختاری که ساخته‌ایم را با استفاده از تابع deflateInit() مقداردهی کنیم، قبل از مقداردهی باید یک‌سری مقادیر را طبق گفتهٔ مستندات برابر Z_NULL قرار بدهیم : stream.zalloc = Z_NULL; stream.zfree = Z_NULL; stream.opaque = Z_NULL; return_ = deflateInit(&stream, *level); if (return_ != Z_OK) return return_; در اینجا مقدار level می‌تواند چیزی بین -1 تا 9 باشد، هرچه مقدار کم‌تر باشد سرعت فشرده‌سازی بالاتر است و مقدارفشرده‌سازی کم‌تر. مقدار صفر هیچ فشرده‌سازی‌ای انجام نمی‌شود و صرفاً یک فایل با فرمت zlib درست می‌شود. ماکروی Z_DEFAULT_COMPRESSION برابر با -1 هست که سرعت و فشره‌سازی خوبی را فراهم کند. در تست قبلی خودم مقدار را برابر Z_DEFAULT_COMPRESSION گذاشتم و این‌بار می‌خواهم برابر ۹ بگذارم. خب حالا باید بریم سراغ فشرده‌سازی، در این قسمت ما یکdo-while می‌نویسیم که جریان‌ورودی را تا EOF (انتهای فایل) بخواند : do{ stream.avail_in = fread(input, 1, CHUNK, source); if (ferror(source)){ deflateEnd(&stream); return Z_ERRNO; } flush = feof(source) ? Z_FINISH : Z_NO_FLUSH; stream.next_in = input; * خب برای دوستانی که با توابع کار با Streamها در سی آشنا هستند، پیشنهاد می‌کنم که این قسمت رو یه‌کمی ازش گذر کنند. اوّل ما از تابع fread استفاده کردیم، این تابع به این‌صورت در فایل stdio.h تعریف شده است : size_t fread( void *restrict buffer, size_t size, size_t count, FILE *restrict stream ); و کاری که می‌کند این است که اوّل یک اشاره‌گر به جایی که باید داده‌‌های خوانده شده ذخیره بشوند می‌گیرید که اینجا ما آرایهٔ input را می‌دهیم، سپس اندازهٔ هر داده‌ای که قرار است خوانده بشود را دریافت می‌کند که یک بایت است هر کاراکتر، آرگومان بعدی مقداری است که باید از Stream خوانده شود که ما به اندازهٔ CHUNKتا می‌خواهیم :). آرگومان آخری نیز که مشخص‌هست. جریانی است که باید داده‌ها خوانده شود. این تابع مقدار داده‌هایی را که با موفقیت خوانده‌است را برمی‌گرداند. که ما آن را در stream.avail_in نگه‌داری می‌کنیم. سپس باید Stream را چک کنیم که خطایی رخ نداده باشد. درصورتی‌که این تابع مقداری غیراز صفر برگرداند مشخص است که خطایی رخ نداده. و درصورتی‌که خطایی رخ داده‌باشد با استفاده از delfateEnd() جریان را پایان می‌دهیم. و در انتها باید بررسی کنیم که آیا جریان‌ما به EOF (پایان فایل) رسیده‌است یا خیر. که اینکار با استفاده از تابع feof() در هدرفایل stdio.h صورت می‌گیرد. درصورتی‌که پایان‌فایل رسیده باشد مقداری غیر از صفر این تابع بر می‌گرداند. در انتها طبق گفتهٔ مستندات باید اشاره‌گری به داده‌های خوانده شده در next_in قرار بگیرد. که ما اینکار را در خط آخر انجام داده‌ایم. خب در این‌قسمت که ما داده‌ها را از جریان ورودی خواندیم نیاز هست که با استفاده از تابع deflate() عمل فشرده‌سازی را انجام دهیم. این تابع داخل یک حلقهٔ do-while دیگر فراخوانی می‌شود. و تا انتهای داده‌های خوانده شده ادامه می‌دهیم : do{ stream.avail_out = CHUNK; stream.next_out = output; مقدار فضای output‌ما که برای deflate() تهیه شده است توسط avail_out به بایت مشخص می‌‌شود و next_out اشاره‌گری به آن جریان خروجی می‌باشد که در اینجا آرایهٔ output می‌باشد. خب حالا ما باید تابع فشرده‌سازی deflate() را فراخوانی کنیم. این تابع به اندازهٔ avail_in بایت از next_in پردازش می‌کند و به اندازهٔ avail_out بایت در next_out می‌نویسد. که اینجا مقادیر avail_out/in ما برابر با CHUNK می‌باشد و next_out/in ما به آرایه‌های input و output اشاره‌ می‌کند. این حلقهٔ داخلی‌که درست کردیم تضمین می‌کند که تمام داده‌های خوانده‌شده پردازش و نوشته می‌شوند. ورودی‌های تابع deflate() یک اشاره‌گر به ساختار z_stream می‌باشد (همان متغیر stream خودمان) و یک ورودی دیگر که مشخص می‌کند وضعیت و چگونگی flush کردن داده‌ها در output. تابع deflate() تا زمانی‌که مقدار ورودی flush state برابر Z_NO_FLUSH باشد ادامه می‌دهد و وقتی‌که مقدار flush state برابر Z_FINISH تابع deflate() کار را تمام می‌‌کند. این قسمت برای افرادی هست که می‌خواهند کارهای خاصی با این تابع انجام دهند که بدین منظور بهتر است مستندات فنی کتابخانه را مطالعه کنند. return_ = deflate(&stream, flush); assert(return_ != Z_STREAM_ERROR); در اینجا ما با استفاده از ماکروی assert که در هدرفایل assert.h تعریف شده است یک شرط می‌گذاریم که درصورتی‌که آن شرط حاصل‌ش برابر صفر باشد مقادیری را در stderr چاپ و با استفاده از abort() برنامه را خاتمه می‌دهد. خب حالا باید مشخص کنیم که تابع ‌deflate() در آخرین فراخوانی چه مقدار خروجی تولید کرده‌است و چه مقدار باقی‌مانده است. و مقادیر تولید شده را داخل جریان خروجی می‌نویسیم : have = CHUNK - stream.avail_out; if (fwrite(output, 1, have, dest) != have || ferror(dest)) { deflateEnd (&stream); return Z_ERRNO; } در اینجا ما با استفاده از تابع fwrite (که ورودی‌های آن مشابه fread می‌باشند) مقدار تولید شده را داخل جریان خروجی می‌نویسیم. این تابع باید تعداد مقادیری که با موفقیت نوشته شده‌اند را به بایت بر گرداند. پس بررسی می‌کنیم که اگر برابر با have نبود یا اینکه برای جریان dest خطایی رخ داده است. برنامه را خاتمه دهد. تابع deflate() تا جایی که بتواند به کارخود ادامه می‌دهد و زمانی که دیگر داده‌ای برای پردازش نداشته‌باشد مقدار avail_out برابر صفر قرار می‌گیرد و مقدار Z_BUF_ERROR را بر می‌گرداند. و ما می‌توانیم از حلقهٔ داخلی خارج شویم : } while (stream.avail_out == 0); assert(stream.avail_in == 0); خب ما با بررسی متغیر flsuh می‌توانیم وضعیت پایان فایل را متوجه بشویم، درصورتی‌که مقدار این متغیر برابر Z_FINISH باشد کار ما تمام شده‌است و می‌توانیم از حلقه خارج شویم : } while (flush != Z_FINISH); assert(return_ == Z_STREAM_END); و در انتها کافی است که حافظه‌ای که دریافت شده آزاد شود، و مقدار Z_OK از تابع برگرداننده شود : deflateEnd(&stream); return Z_OK; } خب تابع compress ما به اتمام رسید، حال باید بریم سروقت تابع decompress،‌ این تابع شباهت بسیار زیادی به تابع قبلی دارد : int decompress (FILE *source, FILE *dest); و حالا متغیر‌های‌محلی را دوباره تعریف می‌کنم، اینجا دیگر نیازی به متغیر flush نیست چرا که خود توابع zlib پایان کار را مشخص می‌کنند : { int return_; unsigned have; z_stream stream; unsigned char input[CHUNK]; unsigned char output[CHUNK]; و حال نیاز هست که زمینهٔ تخصیص حافظه را فراهم کنیم : stream.zalloc = Z_NULL; stream.zfree = Z_NULL; stream.opaque = Z_NULL; stream.avail_in = 0; stream.next_in = Z_NULL; return_ = inflateInit(&stream); if (return_ != Z_OK) return return_; اینجا مقدار avail_in برابر صفر و مقدار next_in برابر Z_NULL قرار می‌گیرد تا مشخص شود که هیچ ورودی فراهم نشده است. حالا باید حلقهٔ معروف خودمان را درست کنیم و با استفاده از تابع inflate() اقدام به Decompressing کنیم : do { stream.avail_in = fread(input, 1, CHUNK, source); if (ferror(source)){ inflateEnd(&stream); return Z_ERRNO; } if (stream.avail_in == 0) break; stream.next_in = input; خب با توجه به توضیحات تابع قبلی این دستورات نیز عملکردشان مشخص است. حال باید حلقهٔ‌داخلی را بنویسیم : do { stream.avail_out = CHUNK; stream.next_out = output; حال باید تابع inflate() را برای عمل Decompressing فراخوانی کنیم، دیگر اینجا نیازی به مشخص کردن flush state نداریم چرا که خود zlib به طور خودکار مدیریت می‌کند. تنها چیزی که مهم است، خروجی تابع inflate() می‌باشد که درصورتی‌که برابر Z_DATA_ERROR باشد به معنی این‌است که در داده‌های فشرده‌شده مشکلی وجود دارد. و خروجی دیگر Z_MEM_ERROR می‌باشد که مشخص‌کنندهٔ مشکلی در زمان حافظه‌گیری برای inflate() می‌باشد : return_ = inflate(&stream, Z_NO_FLUSH); assert(return_ != Z_STREAM_ERROR); switch (return_){ case Z_NEED_DICT: return_ = Z_DATA_ERROR; case Z_DATA_ERROR: case Z_MEM_ERROR: inflateEnd(&stream); return return_; } در اینجا خروجی تابع را بررسی کرده و درصورتی‌که خطایی باشد جریان برنامه را خاتمه می‌دهیم. و انتهای حلقه : have = CHUNK - stream.avail_out; if (fwrite(output, 1, have, dest) != have || ferror(dest)) { inflateEnd(&stream); return Z_ERRNO; } } while (stream.avail_out == 0); و زمانی‌که خروجی‌ه inflate() برابر Z_STREAM_END باشد، یعنی اینکه دیگر کار تمام شده و داده‌ای برای پردازش نمی‌باشد : } while (return_ != Z_STREAM_END); تا این قسمت دیگر کار استخراج به پایان رسیده‌ است . و کار تابع decompress را تمام می‌کنیم : inflateEnd(&stream); return (return_ == Z_STREAM_END) ? Z_OK : Z_DATA_ERROR; } خب تمام شد !. ما دوتابع compress و decompress که مستقیم از zlib استفاده می‌کنند را به پایان رساندیم. حال بیاید از آنها استفاده کنیم. در وهلهٔ اوّل نیاز است که تابعی داشته‌باشیم تا خروجی این توابع را برای ما مدیریت کنند : void zlibError(int return_) { fprintf(stderr, "ZLIB ERROR: "); switch (return_) { case Z_ERRNO: if (ferror(stdin)) fprintf(stderr, "ERROR READING stdin.\n"); if (ferror(stdout)) fprintf(stderr, "ERROR WRITING stdout.\n"); break; case Z_STREAM_ERROR: fprintf(stderr, "INVALID COMPRESSION LEVEL.\n"); break; case Z_DATA_ERROR: fprintf(stderr, "INVALID OR INCOMPLETE deflate() DATA.\n"); break; case Z_MEM_ERROR: fprintf(stderr, "OUT OF MEMORY.\n"); break; case Z_VERSION_ERROR: fprintf(stderr, "zlib VERSION MISMATCH.\n"); } } و حال تابع main برنامهٔ ما : int main(int argc, char **argv) { int return_; if (argc == 1) { return_ = compress(stdin, stdout, 9); if (return_ != Z_OK) zlibError(return_); return return_; } else if (argc == 2 && strcmp(argv[1], "-d") == 0) { return_ = decompress(stdin, stdout); if (return_ != Z_OK) zlibError(return_); return return_; } else { fprintf(stderr, "zlib Usage: PROGRAMM [-d] < SOURCE > DEST\n"); return EXIT_FAILURE; } return EXIT_FAILURE; } و برای کامپایل باید موقعیت کتابخانهٔ zlib را مشخص کنیم : $> gcc main.c -L. -lz -O3 -o zlib خب حالا بیاید با هم این برنامه را اجرا کنیم :). قبل از اجرا نیاز است که ما یک فایل حجیم داشته‌باشیم، برای اینکار کافیه که به این‌صورت یکی درست کنیم : $> yes "iostram.ir" > huge.file بهتر از بعد از چند ثانیه با استفاده از Ctrl + C برنامه را خاتمه دهید، برای من بعد از ۱۱ ثانیه برنامهٔ yes فایلی به اندازهٔ ۶۲۹ مگابایت، محتوی iostream.ir درست کرد. حالا بریم برای فشرده‌سازی : $> time ./zlib < huge.file > huge.file.comp real 0m13.560s user 0m5.785s sys 0m0.375s من این برنامه با استفاده از برنامهٔ time اجرا کردم تا زمان مصرفی را مشاهده کنم، که بعد از ۱۳ ثانیه به اتمام رسید. حال بیاید بیبنیم حجم خروجی چقدر است ! $> ls -ltrh total 631M -rw-r--r-- 1 ghasem ghasem 94K Jan 15 2017 zlib.h -rwxr-xr-x 1 ghasem ghasem 119K May 10 10:59 libz.so.1.2.11 lrwxrwxrwx 1 ghasem ghasem 14 May 10 10:59 libz.so.1 -> libz.so.1.2.11 lrwxrwxrwx 1 ghasem ghasem 14 May 10 10:59 libz.so -> libz.so.1.2.11 -rw-r--r-- 1 ghasem ghasem 3.5K May 10 14:39 main.c -rwxr-xr-x 1 ghasem ghasem 18K May 10 14:40 output -rwxr-xr-x 1 ghasem ghasem 18K May 10 14:46 zlib -rw-r--r-- 1 ghasem ghasem 629M May 10 14:46 huge.file -rw-r--r-- 1 ghasem ghasem 1.3M May 10 14:47 huge.file.comp واقعاً عالی بود. حجم فایل خروجی برابر با 1.3 مگابایت است. یعنی یک مگابایت و ۳۰۰ کیوبایت. حال بیاید از حالت فشرده خارج کنیم فایل را : $> time ./zlib -d < huge.file.comp > huge.file.dcomp real 0m12.556s user 0m0.818s sys 0m0.472s بعد از تنها ۱۳ ثانیه یک فایل ۶۲۹ مگابایتی برایمان درست کرد. که عیناً برابر فایل اوّلی می‌باشد. باور نمی‌کنید ؟ خب بیاید sha1sum آنها برررسی کنیم : $> sha1sum huge.file 3c02d5bd13b91f0e663d63d11ee33a2e71126615 huge.file $> sha1sum huge.file > huge.file.sha1 $> sha1sum huge.file.dcomp > huge.file.dcomp.sha1 $> cat huge*.sha1 3c02d5bd13b91f0e663d63d11ee33a2e71126615 huge.file.dcomp 3c02d5bd13b91f0e663d63d11ee33a2e71126615 huge.file سورس کامل برنامه را از این‌قسمت می‌توانید بارگیری کنید. - موفق‌وپیروز باشید ?
  16. فایل‌ها/تغییرات پروژه را چطوری کنترل کنیم ؟ در وهلهٔ اوّل شاید بگید چه نیازیه ؟ خب برنامه رو می‌نویسیم و میریم دیگه !. درسته برنامه‌اتون را می‌نویسید و می‌روید؛ امّا به کجا چنین شتابان ؟ آیا همیشه برنامهٔ شما کوچک‌خواهد بود ؟ آیا قراره برنامهٔ شما در صد خط تمام بشه ؟ یا اینکه کلاً قصد توسعه‌اش رو دیگه ندارید ؟ خب شاید یکی دیگه داشت :). فرض کنید برنامهٔ‌تان را نوشتید : void parsing(int argc, char **argv){ top = 0; for (unsigned i=1; i < (unsigned)argc; i+=2){ listArgs[top].name = argv[i]; listArgs[top].value = argv[i+1]; top++; } } خب، برنامه‌کار می‌کنه و میرید و یک هفته‌ٔ دیگه میاید و مثلاً خط : top = 0; را حذف می‌کنید. و برنامه در اجرای اوّل درست کار می‌کنه؛ پیش‌خودتون می‌گید خب چه نیازی بود، الکی ماهم کد نوشتیم :). امّا بعداً در اجراهای متوالی برنامه شروع می‌کنه به دادن خروجی‌های نامتعارف. اینجاس که باید ساعت‌ها وقت بزارید و بگردید ببینید آخرین‌بار چه چیزی رو تغییر دادید و کدوم فایل را ویرایش کردید. کار مسخره‌ و اعصاب‌خورد کنی میشه، درسته ؟. امّا برای مدیریت این دَنگٌ‌وفَنگ‌ها می‌تونید از سیستم‌های‌مدیریتپروژه‌ استفاده بکنید. مثل Git حالا اینکه چرا گیت ؟ به خاطر اینکه راحت‌ترینه و بهترینه. چرا ؟ چون امتحانش را پس داده، پروژهٔ بزرگ "کرنل‌لینوکس" را داره مدیریت می‌کنه. حالا بیاید ببینیم اگه ما ازت گیت (git) استفاده می‌کردیم، چطوری می‌توانستیم بفهمیم که چه بلایی سر کد آمده : ۱- اوّل گزارشات را چک می‌کنم، تا ببینم آخرین گزارشی که از تغییرات ذخیره کردم چه بوده ؟: $> git log commit bb513a5f9ec429222de03afa690e7fa5d2fbdf6e (HEAD -> master) Author: Ghasem Ramezani <g1999ramezani@gmail.com> Date: Sun May 5 00:05:22 2019 +0430 create a bug commit ab176fa8a282a74e6badfc285c0986bc66ee6b7d (origin/master, origin/HEAD) Author: Ghasem Ramezani <g1999ramezani@gmail.com> Date: Sat May 4 10:40:32 2019 +0430 make `top` to be 0 at first of parsing() function and make class storage of listArgs to be `extern` and getOption() function return "NULL" on Failure. خب فهمیدم که آخرین تغییرم با عنوان create a bug ثبت شده، حالا باید از شناسه‌اش استفاده کنم. ۲- تغییراتی که در آن گزارش ثبت شده است را مشاهده می‌کنم. : $> git show bb513a5f9ec429222de03afa690e7fa5d2fbdf6e commit bb513a5f9ec429222de03afa690e7fa5d2fbdf6e (HEAD -> master) Author: Ghasem Ramezani <g1999ramezani@gmail.com> Date: Sun May 5 00:05:22 2019 +0430 create a bug diff --git a/source/arg.c b/source/arg.c index c776ff2..a75c91d 100644 --- a/source/arg.c +++ b/source/arg.c @@ -7,7 +7,6 @@ unsigned top=0; struct ARGS listArgs[MAX_ARG]; void parsing(int argc, char **argv){ - top = 0; for (unsigned i=1; i < (unsigned)argc; i+=2){ listArgs[top].name = argv[i]; listArgs[top].value = argv[i+1]; دیدی به چه سادگی توانستیم تغییری که دادیم را پیدا کنیم ؟ البته این انتهای ماجرا نیست ! الآن که متوجه شدیم در کدام گزارش‌ما خراب‌کاری کردیم؛ کافیه که تغییرات را به گزارش قبل از خراب‌کاری برگردانیم : $> git reset --hard ab176fa8a282a74e6badfc285c0986bc66ee6b7d البته قابل ذکره که ما اینجا تنها داخل این گزارش فقط یک تغییر داشتیم، مسلماً کار می‌تونه کمی پیچیده‌تر بشه اگه تغییرات زیاد باشن، که همیشه هستن ?. چگونه با گیت (git) کار کنیم ؟ بسیار ساده، مسلماً اوّل نیاز دارید که این برنامه را نصب کنید. این برنامه به طور پیش‌فرض در سیستم‌عاملتون نصب نیست. کافیه که از مدیربستهٔ سیستم‌عاملتون کمک بگیرید، مثلاً برای Debian - Ubuntu - Ubuntu Mint به این‌صورت کار تمام می‌شود : $ apt install git حالا بعد از نصب، نیاز دارید که مشخصاتتان را ثبت کنید، دقت کنید که تمام توضیحاتی که بنده می‌دهم را می‌توانید به‌صورت کامل‌تر از سایت گیت (git) دنبال کنید. $> git config --global user.name "Ghasem Ramezani" $> git config --global user.email "g1999ramezani@gmail.com" $> git config --global core.editor emacs دو مورد اوّل که واضح هستن، امّا مورد آخر دل‌بخواه خودتان هست، زمانی‌که نیاز باشه گیت (git) ویرایشگرمتنی را جهت ویرایش‌باز بکند باید بداند که کدام ویرایشگر مورد علاقهٔ شماست. می‌توانید هر برنامه‌ای را قرار بدهید. امّا دقت کنید که بهترین ویرایشگر‌ها می‌توانند Vim, Emacs, Notepad++ باشند؛ فایل این تنظیمات را می‌توانید از این مسیرها دنبال کنید : User Space : ~/.gitconfig System Wide: /etc/gitconfig ساخت مخازن (repository) خب حالا که نصب/پیکربندی انجام شد، کافیه که مخزن (repository) خودمان را راه‌اندازی کنیم. یک پروژهٔ جدید درست کنید و گیت (git) را مقداردهی (Initialize) کنید : $> mkdir project ; cd project $> git init ما یک دایرکتوری به اسم project درست کردیم، و مخزن (repository) خودمان را با دستور git init راه‌اندازی کردیم، یک سری فایل‌هایی گیت (git) برای ما داخل آن دایرکتوری با اسم .git درست کرده. تغییراتی‌که نیاز رو انجام میدیم، مثلاً در وهلهٔ اوّل دایرکتوری‌ها و فایل‌های پروژه را راه‌اندازی می‌کنیم : $> mkdir header source build object $> touch header/arg.h source/arg.c Makefile $> tree . ├── build ├── Makefile ├── header │ └── arg.c ├── object └── source └── arg.h 4 directories, 3 files $> اگه درمورد Makefile نمی‌دانید، می‌توانید از اینجا با GNU Make و Makefile آشنا بشید. الآن بد نیست که خروجی دستور git status را ببینیم تا توضیحاتی در این‌باره بدیم (این دستور، وضعیت‌جاری مخزنمان را نشان می‌دهد) : $> git status On branch master No commits yet Untracked files: (use "git add <file>..." to include in what will be committed) Makefile header/ source/ nothing added to commit but untracked files present (use "git add" to track) عکس زیر را مشاهده‌کنید تا توضیح‌بهتری بدم : فایل‌های شما داخل گیت (git) دارای حالات‌های مختلفی‌هستن، به طورکلّی یا شناخته‌شدن‌اند (tracked) یا ناشناخته‌اند (untracked)؛ فایل‌ها/دایرکتوری‌هایی که ما بعد از مقدار‌‌دهی مخزن‌مان ساختیم، در حالت ناشناخته (untracked) هستند. که خود گیت (git) هم همین‌را به ما گفته‌است : Untracked files: (use "git add <file>..." to include in what will be committed) برای اینکه شناخته‌شده (tracked) بشند، باید آنها را به صحنه (stage) ببریم. برای اینکار خود گیت گفته‌است که باید چه کرد که می‌توانیم به دوصورت انجام دهیم : $> git add Makefile source header $> git add -A خب حالا دوباره خروجی git status را نگاه می‌کنیم : $> git status On branch master No commits yet Changes to be committed: (use "git rm --cached <file>..." to unstage) new file: Makefile new file: header/arg.c new file: source/arg.h الآن فایل‌های ما به stage رفتند، و آمادهٔ این‌هستند که گزارش‌ (commit) بشوند. دقت کنید که Git از خِیر دایرکتوری‌های خالی می‌گذرد. حالا کافیه‌که ما تغییراتی که دادیم را گزارش کنیم، که با دستور git commit به دوصورت انجام می‌شود : $> git commit $> git commit -m "My Message" در حالت‌اوّل، گیت ادیتور پیش‌فرضتان را باز می‌کند و از شما می‌خواهد که یک توضیح‌کوتاه درمورد تغییراتی‌که داده‌اید بنویسید، در حالت‌دوّم، شما مستقیم توضیح‌کوتاه خود را وارد می‌کنید. حال دوباره برگردیم و خروجی دستور git status را ببینیم : $> git status On branch master nothing to commit, working tree clean خیلی‌هم عالی، این نشان دهندهٔ این‌است که ماهیچ فایل ناشناخته (َUntracked) یا دستکاری‌شده (Modified) یا درصحنه (Stage) نداریم. هنگامی‌که تغییراتی را در فایل‌های شناخته‌شده (Tracked) بدید، آن فایل از حالت دستکاری‌نشده (Unmodified) به حالت دستکاری‌شده (Modified) درمیاید، که نیاز است شما تغییرات را درصورت‌نیاز وارد صحنه (Stage) کنید و بعد گزارش‌کنید (Commit). حال تغییراتی‌اعمال می‌کنیم، و مراحل‌مورد نیاز تا درصحنه (Stage) بردن‌فایل‌ها انجام می‌دهیم : $> git add -A $> git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) modified: Makefile modified: header/arg.h modified: source/arg.c امّا شاید شما نخواید که مثلاً Makefile گزارش‌ش با مابقیه فایل‌ها یکی باشه، و نیاز دارید که از Stage بیرون بیاریدش؛ دقّت کنید خود Git هم راهنمایی‌ کرده که باید چه‌کار کرد : $> git reset HEAD Makefile $> git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) modified: header/arg.h modified: source/arg.c Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: Makefile و حالا می‌توانید به راحتی گزارش فایل‌های خودتان را برای header/arg.h و source/arg.c بنویسید : $> git commit -m "Done With Print() Function" فایل .gitignore بیاید تا make را اجرا کنیم (درصورتی‌که با GNU Make آشنا نیستید، برای آشنایی این‌قسمت را مطالعه کنید) : $> git status On branch master Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: Makefile Untracked files: (use "git add <file>..." to include in what will be committed) build/ object/ no changes added to commit (use "git add" and/or "git commit -a") اینجا دایرکتوری‌های build و object هم اضافه شدند،امّا ما نیازی نداریم که Git این دایرکتوری‌ها را مدیریت کند، پس کافیه که یک فایل به اسم .gitignore‌ درست کنیم. و فایل‌ها و دایرکتوری‌هایی که نمی‌خواهیم Git آنها را دنبال کند را در آن ذکر کنیم : $> echo -e "/build/*\n/object/*" > .gitignore $> cat .gitignore /build/* /object/ $> git status On branch master Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: Makefile Untracked files: (use "git add <file>..." to include in what will be committed) .gitignore no changes added to commit (use "git add" and/or "git commit -a") و مشاهده می‌کنید که دیگر Git اخطاری برای دایرکتوری‌های build‌ و object نداد. خب دوستان،امیدوارم دلیل اهمیّت Git و کلاً برنامه‌های کنترل‌ورژن را درک‌کرده باشید؛ امّا شرمنده، دنیای Git بزرگ‌تر از آنچه هست که من بخوام خلاصه‌ای از هر قسمت را در یک پست باز‌گو کنم؛ شدیداً پیشنهاد می‌کنم که این‌کتاب را برای فراگیری هرچه بهتر Git‌ بخوانید. - موفق و پیروز باشید. ?
  17. قاسم رمضانی منش

    بیلد سیستم یا Make چیست ؟

    خب ! Build System چیست ؟ تمام برنامه‌هایی که می‌نویسیم، معمولاً یک main.c دارند که نقطهٔ‌شروع (start point) برنامهٔ‌ما هست. آیا همیشه همین یک فایله ؟ آیا همیشه نیازه که به یک‌صورت برنامه‌ را کامپایل کنیم ؟ خب مسلماً جواب "نه" هست. چرا که ممکنه برنامهٔ شما دارای ده‌ها فایل داشته‌باشه، و نیاز داشته‌باشید که هر فایل رو به صورت‌خاصی با فلگ‌های خاصی کامپایل‌کنید. اینجاس که "بیلد سیستم‌"ها وارد کار میشوند. به احتمال زیاد نمونه‌های زیادی مشاهده کردید که وقتی یک سورسی‌را (source) از مخازن آنلاین گیت، مثل گیت‌هاب یا گیت‌لب دریافت می‌کنید در فایل‌ راهنما (README.md) در بخش Build نوشته که وارد دایرکتوری بشید و دستور make و بعد make install را وارد کنید، دقیقاً کاری که می‌کنید اینکه برنامهٔ GNU Make را صدا می‌زنید که فایل تنظیمات رو از دایرکتوری جاری بخواند و دستورات تعیین شده رو انجام بده، این دستورات در فایلی به نام Makefile‌ نوشته میشود. نصب کردن GNU Make این برنامه معمولاً روی تمام سیستم‌عامل‌های معقول مثل GNU/Linux یا اقوام BSD نصب هست، درصورتی‌که نبود می‌توانید با استفاده از مدیربسته‌ٔ سیستم‌عاملتون اقدام به نصب کنید، مثلاً برای نصب روی سیستم‌عامل Debian - Ubuntu - Ubuntu Mint می‌توانید به این‌صورت عمل کنید : $> apt install make چه کنیم با GNU Make ؟ اوّل از همه باید یک برنامه‌ای داشته‌باشیم که بخوایم براش Build System تعیین کنیم و دستورات Makefileش رو بنویسیم. یک نمونهٔ ساده کد چند تکه‌ای را می‌توانید از این‌قسمت دریافت کنید. ما سه فایل arg.c/arg.h و main.c را به این‌صورت داریم (یک ساختار معقول) : . ├── build ├── obj └── src ├── arg.c ├── arg.h └── main.c خب حالا ما باید Makefile خودمان را داخل دایرکتوری ریشه درست کنیم، قبلاً هم گفتم : "برنامهٔ GNU Make به دنبال فایلی به اسم Makefile یا GNUmakefile یا makefile می‌گرده". در Makefile می‌توانیم‌ما قوانین (rule) برای ساخته شدن چیزی و متغیر‌هایی تعریف کنیم. اینجا من توضیحات خلاصه‌ای را می‌گویم، باقی‌ماندهٔ مطالب را باید از مستندات‌رسمی GNU Make یا راهنمای سریع دنبال کنید. هر قوانین‌ای که تعریف می‌کنیم دارای این ساختار هست : نیازها : هدف‌ها دستورات مثلاً ما می‌خواهیم که برنامهٔ‌کامپایل شدهٔ‌مان، با اسم args در دایرکتوری build/ قرار بگیره. اینجا "هدف"ما میشه build/args و نیازما هم فایل‌های کامپایل‌ شدهٔ arg.c و main.c هست. اوه ! یک هدف دیگه‌هم پیدا شد؛ الآن هدف دوّم‌ما فایل‌های کامپایل شدهٔ obj/arg.o و obj/main.o هست و نیازمان هم سورس‌های این فایل‌ها یعنی src/arg.h و src/arg.c و src/main.c. خب خیلی زیاد شدن، بهتره که از آخر شروع کنیم و نیازهایمان را برطرف کنیم، اوّلین نیاز فایل‌های کامپایل‌شده هستن : obj/main.o obj/arg.o : src/main.c src/arg.c src/arg.h gcc -c -o obj/main.o src/main.c gcc -c -o obj/arg.o src/arg.o * نکته : سعی نکنید دستورات Makefile را از منطقهٔ کد کپی نکنید، کمی تلاش کنید و بنویسید. خب قبول دارم خیلی زیاد و زشت شد، بیاید این قانون (rule) را به دو تیکه قسمت کنیم : obj/arg.o : src/arg.c src/arg.h gcc -c -o obj/arg.o src/arg.c obj/main.o : src/main.c gcc -c -o obj/main.o src/main.c اگر تا انتها متن ادامه بدید حتماً کوتاه‌ترم خواهد شد :). خب؛ object fileها یا همان فایل‌های کامپایل شده‌‌یمان را به دست‌آوردیم. حالا باید قانون (rule) نیاز اوّلمان را بنویسیم، چه چیزی نیاز داشتیم ‌؟ فایل کامپایل شدهٔ build/args که نیاز به object fileها داشت، حالا object fileها را داریم و باید نیاز هدفمان را برطرف کنیم : build/args : obj/main.o obj/arg.o gcc -o build/args obj/main.o obj/arg.o obj/arg.o : src/arg.c src/arg.h gcc -c -o obj/arg.o src/arg.c obj/main.o : src/main.c gcc -c -o obj/main.o src/main.c تمام شد. ما دستورات Build System خودمان را به زبان برنامهٔ GNU Make نوشتیم؛ حالا کافیه که فقط وارد دایرکتوری‌ای که فایل Makefile هست بشیم و از ترمینال برنامهٔ make را فراخوانی کنیم : $> make gcc -c -o obj/main.o src/main.c gcc -c -o obj/arg.o src/arg.c gcc -o build/args obj/main.o obj/arg.o حالا می‌توانیم برنامهٔ خودمان را اجرا کنیم : $> build/args -name Ghasem -family Ramezani Input Name is [Ghasem] Input Family is [Ramezani] دقّت کرده باشید ما توی نوشتن Makefileمان نیازمندی‌هارو یکی بالاتر از دیگری نوشتیم. چرا ؟ به خاطر اینکه GNU Make میاد از اوّل فایل شروع می‌کنه و قوانین (rules)ها را اجرا می‌کنه. بزارید با یک مثال نشان بدم. Makefile زیر را مدنظرتون داشته‌باشید : obj/arg.o : src/arg.c src/arg.h gcc -c -o obj/arg.o src/arg.c obj/main.o : src/main.c gcc -c -o obj/main.o src/main.c build/args : obj/main.o obj/arg.o gcc -o build/args obj/main.o obj/arg.o ما نیاز اصلی خودمان را آخرین قانون (rule) نوشتیم. حالا برنامهٔ make را اجرا می‌کنیم تا رفتارَش را بهتر متوجه بشیم : $> make gcc -c -o obj/arg.o src/arg.c دیدید ؟ خیلی ساده برخورد کرد، اوّلین قانون (rule) را نگاه کرد تنها نیازمندیش فایل‌های src/arg.c و src/arg.v بودن که وابسته به چیزی نبودند و هدفشان را تأمین کردند. اگر بخواهیم باقی قوانین (rules) را فراخوانی کنیم، باید صراحتاً مشخص کنیم : $> make obj/main.o gcc -c -o obj/main.o src/main.c $> make build/args gcc -o build/args obj/main.o obj/arg.o خب دیگه امیدوارم دلیل اینکه‌ما نیازمندی اصلیه خودمان را اوّلین قانون (rule) قرار دادیم را متوجه شده باشید. وقتی make به نیازمندیه obj/arg.o و obj/main.o برای تأمین build/args برمی‌خوره ادامهٔ قوانین را پیمایش می‌کنه تا نیازمندی‌ها را برطرف کنه. (اگر گیج شدید احتمالاً، پیشنهاد می‌کنم همین‌ موارد را روی کاغذ کشیده و قسمت : نیازمندی‌ها و هدف‌ها و دستورات هر قانون را مشخص کنید.) می‌توانیم قوانینی (rules) تعریف کنیم برای کارهای خاصی، مثلاً همان make install، یعنی قانون install را فراخوانی کن؛ حالا ما قانون clean را برای حذف کردن فایل‌های کامپایل‌شده می‌نویسیم : clean : yes | rm -vf build/* obj/* البته باید در اینجا نکته‌ای را هم حواسمان باشد، باید به GNU Make بگوییم که قانون clean ، یک قانون الکی‌هست، و با یک "هدف" اشتباه نشود. به این‌صورت قانون را ویرایش می‌کنیم : .PHONY : clean clean : yes | rm -vf build/* obj/* نگرانی‌ای هم دربارهٔ Wildcard ها نداشته‌باشید، GNU Make دستتون را باز گذاشته :). متغیرها در GNU Make مسلماً هرجا سخنی از متغیر‌است، سر و کلهٔ راحتی‌کار (و تا حدودی پیچیدگی) پیدا می‌شود. ما می‌توانیم متغیرهم داخل Makefile خودمان داشته‌باشیم. مثلاً فرض کنید که نیاز دارید تمام سورس‌کدها با کامپایل clang و سطح‌بهینه‌سازیه 3 کامپایل بشند. نیازی نیست‌که هربار اینارو تایپ کنیم. کافیه براشون متغیرتعریف کنیم : CC = clang OP = -O3 OBJECT = obj/main.o obj/arg.o ARGS = src/arg.c src/arg.h build/args : $(OBJECT) $(CC) $(OP) -o build/args $(OBJECT) obj/arg.o : $(ARGS) $(CC) $(OP) -c -o obj/arg.o src/arg.c obj/main.o : src/main.c $(CC) $(OP) -c -o obj/main.o src/main.c clean : yes | rm -vf build/* obj/* متغیرهای به خصوصی نیز در GNU Make تعریف شده‌اند که می‌توانند کار مارا بسیار راحت‌تر کنند،‌ برای مثال می‌توانیم قانون object file‌ها را به اینصورت بازنویسی کنیم : obj/%.o : src/%.c $(CC) $(OP) -c -o $@ $? برای اطلاعات بیشتر به راهنمای‌سریع GNU Make مراجعه کنید. یادداشت‌ها یا Code Comments برای استفاده از قابلیت Comment گذاری در کد، کافیه که اوّل خط خودتون از کاراکتر # استفاده کنید. خب دوستان، سعی کردم کلیّات مبحث را بگم؛ ابزار Make قابلیت‌های بسیار زیادی داره که حتماً باید خودتون مطالعه کنید. مثلاً خواستید Makefile شما یک Makefile دیگه را صدا بزنه، یا حتیٰ دستورات شرطی اجرا بکند و یا از همه مهم‌تر بر اساس معماری پلتفرم شما عملیات کامپایل را انجام بده و ... . - موفق‌وپیروز باشید. ?
  18. قاسم رمضانی منش

    سلام؛ مطمئن بشید که از نسخهٔ درست استفاده می‌کنید و اینکه این ماژول برای شما ساخته (بیلد) شده باشد؛ درغیراینصورت باید خودتان بسازید. اگر می‌خواید از دل‌متن مقداری را بیرون بکشید بهترین‌کار این است که از QRegExp استفاده کنید.
  19. قاسم رمضانی منش

    سلام؛ بد نبود اگر نیَّت شما توسعهٔ‌نرم‌افزارهای-اندروید بوده،‌ از همان فناوری QtQuick استفاده می‌کردید. خب، برای آشنا شدن به مباحث و روش استفاده از Layoutها تنها راه این‌است که از همان پیوندی‌که آقای‌اسدزاده دادند : https://doc.qt.io/qt-5/layout.html شروع کنید و مطالعه‌کنید، خوش‌بختانه این‌بخش‌هم دارای مثال‌های خوبی هست که می‌تونید با تمرین و تست‌وخطاهایی نتایج خوبی بگیرید. کافیه‌که کاربرد هر Layout را متوجه شده و بدانید که در کجا باید استفاده کنید. قطعه‌ وید‌ئوهایی هم داخل youtude موجود می‌باشد که دیدنش بدنیست :).
  20. قاسم رمضانی منش

    سلام؛ این سوأل شما بسیار بسیار کلّی هست ! ، تا قسمت نوشتن برنامهٔ‌خود که مرتبط به زبان‌برنامه‌نویسی سی‌پلاس‌پلاس هست. ولی قسمت کامپایل و اجرای آن برنامه به عهدهٔ سیستم‌عامل شماست؛ بهتر بود که ذکر کنید از چه سیستم‌عاملی و چه نسخه‌ای استفاده می‌کنید. حال بنده به صورت کلّی توضیحاتی می‌دهم امیدوارم مفید باشد : - درصورتی‌که از سیستم‌عامل‌های GNU/Linux استفاده می‌کنید، شما باید مستندات دسکتاپ خودتان را بررسی کنید هر دسکتاپی که استفاده می‌کنید بنا به قواعد خودش فایل‌ها/دستوراتی را در شروع دسکتاپ اجرا می‌کند. امّا اگر دسکتاپی ندارید ولی از X استفاده می‌کنید، بهتر است یک فایل .desktop درست کنید و آن را در یکی از این مسیرها قرار بدهید : User : ~/.config/autostart SystemWide : /etc/xdg/autostart نمونه‌های از قبل نوشته‌ شدهٔ فایل‌های .desktop را می‌توانید در این آدرس پیدا کنید و یا یکی را خودتان بنویسید : User : ~/.local/share/applications SystemWide : /usr/share/applications اگر از مدیرپنجره‌هایی مثل i3wm یا awesome استفاده می‌کنید، می‌دانید که این‌ها فایل‌های پیکربندی مختص به خود دارند که در آنها نیز می‌توانید دستورات اجرایی خودتان را قرار بدید. - درصورتی‌که از سیستم‌عامل Mac OS استفاده می‌کنید، به این لینک مراجعه کنید. - درصورتی‌که از نرم‌افزار مایکروسافت‌ویندوز استفاده می‌کنید، منوی Start را باز کرده و ابزار Run را اجرا کنید - می‌توانید این‌کار را با زدن دکمه‌های WinKey + R خلاصه‌کنید - و بعد عبارت: shell:startup را وارد کنید، پنجرهٔ Windows Explorer باز می‌شود، هر فایلی که در آن دایرکتوری قرار بدهید در هنگام Login User اجرا می‌شود.
  21. قاسم رمضانی منش

    سلام؛ می‌توانید از QKeyEvent استفاده کنید، امّا این نیازمند gui است. برای محیط‌متنی می‌توانید خود یک کلاس برای شناسایی کلید زده شده بنویسید و آن را در یک QThread اجرا کنید تا زمانی که کلید مورد نظر شناسایی شد عملی که نیاز دارید انجام شود. - GNU Readline Library - Detect Space Key
  22. قاسم رمضانی منش

    سلام ؛ Container : این اسامی‌ای که اسم بردید. تماماً Continer هستند. و وظیفه نگه‌داری داده‌هارا دارند. برای اینکه برنامه‌نویس دستش باز باشه در مدیریت داده‌ها در زبان برنامه‌نویسی سی‌پلاس‌پلاس می‌تونید از این کانتینرها در جهت نگه‌داری داده‌هاتون استفاده کنید. که استفاده از توابع و کلاس‌های موجود در هدرفایل algorithm در کنار این کانتینرها پیشنهاد میشود و می‌تواند قدرت کنترل بیشتری به برنامه‌نویسی بر روی داده‌های خود بدهد. که خود بسیار در پرفورمنس (به انگلیسی : performance) برنامه تاثیر دارد. Qt Container : از آن‌جایی که Qt یک فریمورک هست. برای اینکه برنامه‌نویسی به راحتی بتواند با کلاس‌ها و توابع این فریمورک تعامل برقرار کند ؛ Qt یک‌سری از مخازن (معادل فارسی کانتینر) را بازنویسی کرده‌است. که می‌توانید در این لینک بیشتر در این‌‌باره مطالعه کنید.
  23. قاسم رمضانی منش

    علیکم‌السلام ؛ قبل از اینکه به پاسخ مستقیم سؤال بپردازیم ، بیاید درک کنیم چه شد که template ها نیاز برنامه‌نویس شد. این تابع را در نظر بگیر : int Sum (const int& first, const int& second){ return first+second; } این تابع دو عدد از نوع int دریافت و حاصل جمع‌شون را بر می‌گردونه. خب ! حالا چه میشه اگه شما بخواید به جای int نوع double ارسال کنید ؟ و برنامه‌ی شما با انواع مختلفی داده نیاز داشته باشه که با این تابع کار کنه. شاید بگید خب یک تابع دیگه با پارامتر و نوع بازگشتی double می‌نویسم. با این روش چه مشکلاتی پیش می‌آید : کد تکراری. بالا رفتن درصد خطای کد. افزایش بی‌دلیل حجم کد. سخت شدن مدیریت کد. مشکل در آپدیت کردن سورس کد. و ... زبان سی‌پلاس‌پلاس برای رفع این مشکل‌ما template ها را معرفی کرده است. template ها می‌توانند هر نوع داده‌ای را قبول کنن. تابع زیر ، تابع template بازنویسی شده‌ی تابع Sum است که در مثال بالا نوشتیم : template <typename newType> newType Sum (const newType& first, const newType& second){ return first + second; } ???خییییلی بزرگ شد ن ؟. خب می‌تونیم کوتاهش کنیم : template <typename newType> newType Sum (const newType& first, const newType& second){ return first + second; } نکته : می‌دانیم که کامپایلر فاصله‌هارا نادیده می‌گیره. خب ! اوّل برای تبدیل تابع ، به یک تابع template باید از کلمه‌ی کلیدی template استفاده کنیم. و بعد با استفاده از < > اسم نوع داده‌های جدیدمان را بنویسیم. نکته : ما هیچ نوع داده‌ی جدیدی درواقع تعریف نمی‌کنیم. این تعریف فقط معرفی یک اسم به عنوان نوع داده‌ای هست که می‌تواند به هر چیزی اعم از int,double ,... تبدیل بشود. و داخل < > ما از کلمه‌ی‌کلیدی typename برای تعریف اسم داده‌‌ استفاده کردیم. و همچنین می‌توانیم کلمه‌ی‌کلیدی class نیز استفاده کنیم که هردو برابر یک دیگر هستن. امّا زمانی شما دارید templateی از template درست می‌کنید. مثلاً این نمونه : template < template <typename> class MyTmpClass, typename newType> اینجا دیگه نمی‌توانید بین کلمه‌کلیدی typename و class تفاوت قائع نشید. چرا که نیازه صراحتاً مشخص. امّا در استاندارد ۱۷ این مشکل وجود ندارد ِ؛ ولی بهتر است که این تفاوت را اعمال کنیم. حال به راحتی می‌توانیم تابع را فراخوانی کنیم. به تکه کدزیر توجه کنید Sum (12 ,32); Sum (1'000'000'000'000,3212'333'233); Sum (std::string("ghasem") , std::string("ramezani")); Sum (12, 32.0); سه خط اوّل کد بدون مشکل کامپایل می‌شوند. الا خط ‌آخری. چرا که ما یک پارامتر با نوع int و یک پارامتر با نوع double‌ ارسال کردیم. برای حل این مشکل بیاید ببینیم که template ها به چه صورت کار می‌کنند. فرض کنید این تابع را فراخوانی کردیم : Sum ( int , int ); وقتی کامپایلر به این تابع بر می‌خورد. تابع template مارا به این‌صورت برای ما بازنویسی می‌کند : int Sum (const int& first, const int& second){ return first + second; } خب ! و وقتیما سعی می‌کنیم این تابع را فراخوانی کنیم : Sum ( int , double ); برنامه کامپایل نمی‌شود و خطای زیر را ساطع میکنن. چرا که یک پارامتر Int و دیگری double هست. ./main.cpp:5: candidate template ignored: deduced conflicting types for parameter 'newType' ('int' vs. 'double') آیا این شکل استفاده از توابع براتون آشنا نیست ؟ یه کم فک کنید : std::vector std::array std::list تمام این کلاس‌ها ، template class هستند. و هر نوعی می‌توانند قبول کنند : #include <vector> #include <string> class myClass{ public: myClass(){} }; int main (void){ std::vector <int> myIntger {1,2,3,4}; std::vector <std::string> myString {"ghasem" , "ramezani"}; std::vector <myClass> myClassVector; return 0; } نکته : این توضیح خیلی خلاصه از template ها و روش استفاده از آنها بود و به همین دو خط محدود نمی‌شوند. و اینکه گفتید "به نظر پیچیده می‌باشد" ?. پیچیدگی در عین سادگی یکی از قابلیت‌های فوق‌العاده‌ی زبان سی‌پلاس‌پلاسه?. اصلاً کد باید وحشت‌ناک باشه.?
  24. قاسم رمضانی منش

    چگونه کد خود را زیبا کنیم؟

    با سلام. در این مقاله قصد بر معرفی چند ابزار کاربردی برای برنامه‌نویسان گرامی را داریم. پس با ما همراه باشید.? |Carbon| قبل از هر چیزی کمی به کد زیر نگاه کنید : #include <KWayland/Server/output_interface.h> #include <KWayland/Server/outputdevice_interface.h> namespace KWayland { namespace Server { class OutputInterface; class OutputDeviceInterface; class OutputChangeSet; class OutputManagementInterface; class XdgOutputInterface; } } namespace KWin { namespace ColorCorrect { struct GammaRamp; } آیا شما واقعاً رغبت به خواندن این کد می‌کنید ؟ صد در صد خیر. چرا که هیچ indent یا code highlight در نظر گرفته نشده‌اس ! در نظر بگیرید اگر ۱۰۰۰ خط کد بود ?. در اینجا وب‌سایت Carbon به ما اجازه قالب‌بندی ، تغییر فونت ، رنگ‌بندی و... کدهارا میدهد. Carbon تعداد زیادی از زبان‌های برنامه‌نویسی را اعم از C\C++ , Python , JS ,... پشتیبانی میکند. برای شخصی‌سازی تم مورد علاقه خود. کافی است که تنظیمات خود را اعمال و بعد url سایت را ذخیره کنید.این یک نمونه از تنظیماتی است که بنده اعمال کردم : حال به راحتی می‌توانید با اسکرین گرفتن از کدزیبای خود آن را برای دوستان خود ارسال کنید. |Beautify Tools| سایت Beautify Tools مجموعه‌ای عظیم ابزارهایی اعم از : Beautifiers And Minifiers Converters String Utilities CSS Preprocessors Utilities Unit Converters Cryptography ... می‌باشد. که بسیار به کمک برنامه‌نویسان وب می‌آید. برای مثال شما یک فایل حجیم CSS دارید. که برای راحتی و قابل‌خواندن‌بودن کد از indent های بسیار استفاده کرده‌اید که خود باعث بالا بردن حجم نهایی فایل شده‌اند. به راحتی با استفاده از ابزار CSS Beautifier می‌توانید تمام indent های بی‌مصرف را از بین ببرید : |Artistic Style| Artistic Style یک ابزار فرم‌دهنده (indenter) برای زبان‌های C, C++, C++/CLI, Objective‑C, C#, and Java می‌باشد. برای نصب و استفاده به مستندات نصب این ابزار مراجعه کنید. |Clang Format| Clang Format یک ابزار command-line بسیار قدرتمند برای مرتب کردن ، قالب‌بندی و... کد منبع می‌باشد : ابزار Clang Format فقط به Command Line محدود نمی‌شود. شما می‌توانید به راحتی با IDE مورد علاقه خود این ابزار را تطبیق بدهید. مثل : Vim Qt Creator Visual Studio BBEdit ... برای استفاده به سایت مرجع مراجعه کنید. موفق‌‌ و پیروز باشید.
  25. قاسم رمضانی منش

    با سلام. در نسخه‌های قدیمی Qt Creator امکان این وجود داشت ما به راحتی از کلاس‌هایی مانند QThread ارث‌بری کنیم. اما این قابلیت داخل نسخه جدید Qt Creator محدود به چند کلاس خاص اعم از QObject , QWidget , ... شده است. آیا روشی برای شخصی‌سازی این کلاس‌ها میباشد ؟
×
×
  • جدید...