در قسمتهای قبلی آموزش امبدد C، بیشتر روی مفاهیم پایه زبان C، ساختار برنامهها، حافظه، اشارهگرها و مباحث مهم Bare-metal تمرکز داشتیم. از این قسمت وارد بخشی میشویم که بیشتر به ورودی و خروجی (I/O) در زبان C مربوط است.
در دنیای سیستمهای Embedded، خروجی گرفتن همیشه به سادگی اجرای یک تابع نیست. در یک کامپیوتر معمولی، سیستمعامل و کتابخانههای استاندارد بسیاری از جزئیات را از ما پنهان میکنند؛ اما در سیستمهای Bare-metal، معمولاً خود برنامهنویس باید مسیر خروجی را پیادهسازی کند.
در این قسمت، با printf بهعنوان مهمترین تابع خروجی در زبان C آشنا میشویم، چند نمونه از قالببندی خروجی را بررسی میکنیم، جدول ASCII را با کمک این تابع تولید میکنیم و در پایان با دو جریان خروجی مهم یعنی stdout و stderr آشنا خواهیم شد.

شکل ۱- تفاوت مسیر خروجی در یک کامپیوتر معمولی و یک سیستم Bare-metal
تابع printf یکی از معروفترین و پرکاربردترین توابع زبان C است که برای نمایش خروجی متنی استفاده میشود. این تابع در هدر stdio.h تعریف شده و به ما اجازه میدهد متن، عدد، کاراکتر و مقادیر مختلف را با فرمتهای متنوع چاپ کنیم.
فرم کلی این تابع بهصورت زیر است:
| 1 | printf("format string", argument1, argument2, ...); |
در این ساختار، رشتهی قالب یا همان format string تعیین میکند که هر مقدار چگونه نمایش داده شود. برای مثال:
| 1 2 3 4 5 6 7 8 9 10 | #include <stdio.h> int main(void) { int number = 42; printf("Number = %d\n", number); return 0; } |
خروجی این برنامه بهصورت زیر خواهد بود:
| 1 | Number = 42 |
در اینجا %d به printf میگوید که مقدار بعدی را بهصورت یک عدد دهدهی صحیح چاپ کند.
قدرت اصلی printf زمانی مشخص میشود که بخواهیم نحوه نمایش دادهها را کنترل کنیم. مثلاً میتوانیم تعیین کنیم یک عدد با چه عرضی نمایش داده شود، از چپچین یا راستچین بودن آن مطمئن شویم یا عدد را با صفر پر کنیم.
به مثال زیر توجه کنید:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | #include <stdio.h> int main(void) { int n = 42; printf("Default : |%d|\n", n); printf("Width 5 : |%5d|\n", n); printf("Left : |%-5d|\n", n); printf("Zero pad : |%05d|\n", n); return 0; } |
خروجی:
| 1 2 3 4 | Default : |42| Width 5 : | 42| Left : |42 | Zero pad : |00042| |
در این مثال:
%d عدد را بهصورت معمولی چاپ میکند.%5d عدد را در فیلدی با عرض حداقل ۵ کاراکتر، راستچین چاپ میکند.%-5d عدد را در فیلدی با عرض ۵ کاراکتر، چپچین چاپ میکند.%05d اگر عرض عدد کمتر از ۵ باشد، سمت چپ آن با صفر پر میشود.شکل ۲- مقایسه چهار روش رایج برای چاپ یک عدد در printf
در جدول زیر چند قالب پرکاربرد در printf را میبینید:
| قالب | کاربرد | نمونه خروجی |
|---|---|---|
%d | چاپ عدد صحیح دهدهی | 42 |
%c | چاپ یک کاراکتر | A |
%s | چاپ رشته | Hello |
%x | چاپ عدد در مبنای شانزده | 2a |
%o | چاپ عدد در مبنای هشت | 52 |
%f | چاپ عدد اعشاری | 3.141593 |
برای مثال:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #include <stdio.h> int main(void) { int value = 65; float pi = 3.141593f; printf("Decimal : %d\n", value); printf("Character : %c\n", value); printf("Hex : 0x%x\n", value); printf("Octal : %o\n", value); printf("Float : %f\n", pi); return 0; } |
خروجی:
| 1 2 3 4 5 | Decimal : 65 Character : A Hex : 0x41 Octal : 101 Float : 3.141593 |
یکی از مثالهای آموزشی و جذاب برای تمرین با printf، تولید جدول نویسههای ASCII است. ASCII یک استاندارد قدیمی اما بسیار مهم برای نمایش نویسههاست. هر حرف، عدد یا علامت در این جدول، یک کد عددی مشخص دارد.
برنامه زیر نویسههای قابلچاپ ASCII را به همراه مقدار دهدهی، هگزادسیمال و اکتال آنها چاپ میکند:
| 1 2 3 4 5 6 7 8 9 10 11 | #include <stdio.h> int main(void) { for (char curChar = ' '; curChar <= '~'; ++curChar) { printf("Char: %c Decimal: %3d Hex: 0x%02x Octal: 0%03o\n", curChar, curChar, curChar, curChar); } return 0; } |
بخشی از خروجی برنامه:
| 1 2 3 4 5 | Char: Decimal: 32 Hex: 0x20 Octal: 0040 Char: ! Decimal: 33 Hex: 0x21 Octal: 0041 Char: " Decimal: 34 Hex: 0x22 Octal: 0042 Char: # Decimal: 35 Hex: 0x23 Octal: 0043 Char: $ Decimal: 36 Hex: 0x24 Octal: 0044 |
در این برنامه:
%c نویسه را چاپ میکند.%3d مقدار دهدهی را با عرض حداقل ۳ کاراکتر نشان میدهد.%02x مقدار هگز را با عرض ۲ و صفرگذاری از چپ چاپ میکند.%03o مقدار اکتال را با عرض ۳ و صفرگذاری چاپ میکند.شکل ۳- خلاصهای از نویسههای قابلچاپ ASCII و کدهای آنها
در ظاهر، printf فقط برای چاپ روی صفحه استفاده میشود؛ اما در واقع این تابع خروجی را به جریان استاندارد خروجی یعنی stdout میفرستد. اگر بخواهیم مشخص کنیم خروجی به کدام فایل یا جریان برود، از تابع fprintf استفاده میکنیم.
فرم کلی آن:
| 1 | fprintf(file_pointer, "format", ...); |
برای مثال:
| 1 2 | fprintf(stdout, "Everything is OK\n"); fprintf(stderr, "ERROR: Something bad happened\n"); |
خط اول پیام را به خروجی عادی برنامه میفرستد و خط دوم پیام خطا را به جریان خطا.
در برنامههای C، معمولاً سه جریان استاندارد وجود دارند:
stdin برای ورودیstdout برای خروجی عادی برنامهstderr برای پیامهای خطاتفاوت stdout و stderr در برنامههای واقعی اهمیت زیادی دارد. وقتی خروجی عادی و پیامهای خطا از هم جدا باشند، هم دیباگ آسانتر میشود و هم میتوان در خط فرمان یا اسکریپتها آنها را جداگانه مدیریت کرد.
مثال:
| 1 2 3 4 5 6 7 8 9 | #include <stdio.h> int main(void) { printf("This is a normal message.\n"); fprintf(stderr, "This is an error message.\n"); return 0; } |
خروجی منطقی این برنامه دو بخش دارد:
stdout ارسال میشود.stderr ارسال میشود.شکل ۴- تفاوت جریان خروجی عادی (stdout) و جریان پیام خطا (stderr)
شاید در نگاه اول printf فقط یک ابزار ساده برای نمایش متن به نظر برسد، اما در پروژههای Embedded همین ابزار ساده میتواند نقش بسیار مهمی در دیباگ و بررسی وضعیت برنامه داشته باشد. برای مثال، وقتی یک ماژول میکروکنترلر را از طریق UART به کامپیوتر وصل میکنیم، میتوانیم با استفاده از printf اطلاعاتی مثل موارد زیر را در ترمینال ببینیم:
البته در سیستمهای Bare-metal، تابع printf معمولاً بهصورت پیشفرض کار نمیکند و باید مسیر خروجی آن را خودمان تعریف کنیم؛ مثلاً آن را به UART متصل کنیم.
جمعبندی
در این قسمت با یکی از مهمترین توابع خروجی در زبان C یعنی printf آشنا شدیم و دیدیم که چگونه میتوان با استفاده از قالبهای مختلف، دادهها را به شکلهای متنوع چاپ کرد. همچنین با تولید جدول ASCII، کاربرد عملی قالبهای چاپ را بهتر درک کردیم و در ادامه تفاوت بین stdout و stderr را شناختیم.
در قسمت بعدی، ادامه مباحث ورودی/خروجی را دنبال میکنیم و به سراغ خواندن داده، fgets، sscanf و مقدمات کار با فایلها خواهیم رفت.
تمرین
%d، %5d، %-5d و %05d چاپ کند.printf و یک پیام خطا را با fprintf(stderr, ...) نمایش دهد.
لیست پیوندی در C چیست؟ آموزش Singly Linked...
سیسوگ با افتخار فضایی برای اشتراک گذاری دانش شماست. برای ما مقاله بنویسید.