829755_The_Linux_programming_interface_a_Linux_and_UNIX_system_programming.pdf
6.9 MB
The Linux Programming inTerface
A Linux and UNIX®
System Programming Handbook
Michael KerrisK
@Amin_TechLab
A Linux and UNIX®
System Programming Handbook
Michael KerrisK
@Amin_TechLab
❤4🔥1
رفقا براتون یک سری رفرنس خوب برای آشنایی عمیق از لینوکس فرستادم .
اینکه چی میخونید برق یا کامپیوتر مهم نیست ، صفت بچسبید لینوکس یادبگیرید .
اگر لینوکس بلد بودید به نظرم میدونی کامپیوتر که جلوت هست چه جوری میتونی به بهترین و بهینه ترین شکل ممکن ازش استفاده کنید .
اینو من راجب تمام سیستم عامل های unix بیس میگم ، ولی اگر بلد نبودی به نظرم با اون حساب دار یا انبار داری که اکسل رو باز میکنه تو ویندوزش هیچ فرق نداریم .
اگر لینوکس بدونی ، یعنی سخت افزار رو درک کردی ،
اگر لینوکس بدونی یعنی میفهمی زبان برنامه سطح پایین چیه
اگر لینوکس بدونی یعنی سی بلدی
اگر لینوکس بدونی یعنی مدیریت تسک رو درک کردی
اگر لینوکس بدونی یعنی کامپیوتر رو فهمیدی ....
ارادمند تمام عزیزان 🫡🌹
@Amin_TechLab
اینکه چی میخونید برق یا کامپیوتر مهم نیست ، صفت بچسبید لینوکس یادبگیرید .
اگر لینوکس بلد بودید به نظرم میدونی کامپیوتر که جلوت هست چه جوری میتونی به بهترین و بهینه ترین شکل ممکن ازش استفاده کنید .
اینو من راجب تمام سیستم عامل های unix بیس میگم ، ولی اگر بلد نبودی به نظرم با اون حساب دار یا انبار داری که اکسل رو باز میکنه تو ویندوزش هیچ فرق نداریم .
اگر لینوکس بدونی ، یعنی سخت افزار رو درک کردی ،
اگر لینوکس بدونی یعنی میفهمی زبان برنامه سطح پایین چیه
اگر لینوکس بدونی یعنی سی بلدی
اگر لینوکس بدونی یعنی مدیریت تسک رو درک کردی
اگر لینوکس بدونی یعنی کامپیوتر رو فهمیدی ....
ارادمند تمام عزیزان 🫡🌹
@Amin_TechLab
👍8❤6👾2😢1
This media is not supported in your browser
VIEW IN TELEGRAM
🔍 Unlimited-OCR by Baidu
A powerful OCR model from Baidu that takes things a step beyond DeepSeek-OCR. It features long-horizon parsing, enabling one-shot processing of lengthy documents—including single images, multi-page documents, and entire PDFs.
The model supports Transformers and SGLang, and delivers high-quality structured outputs for document understanding and information extraction.
🔗 github.com/baidu/Unlimited-OCR
@Amin_TechLab
A powerful OCR model from Baidu that takes things a step beyond DeepSeek-OCR. It features long-horizon parsing, enabling one-shot processing of lengthy documents—including single images, multi-page documents, and entire PDFs.
The model supports Transformers and SGLang, and delivers high-quality structured outputs for document understanding and information extraction.
🔗 github.com/baidu/Unlimited-OCR
@Amin_TechLab
👍3
🐍 آشنایی با چند اپراتور کمتر شناختهشده در پایتون
در پایتون، مقادیری که روی آنها عملیات انجام میشود را Operand و نماد انجامدهنده عملیات را Operator مینامیم.
برای مثال در کد زیر:
متغیرهای
اما در میان اپراتورهای پایتون، موردی وجود دارد که خیلی از برنامهنویسها کمتر با آن آشنا هستند: Walrus Operator (
این اپراتور علاوه بر انجام مقایسه یا ارزیابی یک عبارت، همزمان مقدار آن را در یک متغیر نیز ذخیره میکند.
مثلاً معمولاً کدی شبیه این مینویسیم:
اما با استفاده از Walrus Operator میتوان همان کار را به شکل خلاصهتر انجام داد:
نکته جالب اینجاست که فارغ از نتیجه شرط، متغیر
حالا سراغ یک اپراتور عجیبتر برویم؛ چیزی که احتمالاً هرگز در کدهای واقعی ندیدهاید!
در نسخههای قدیمی پایتون، برای عملگر «نامساوی» بهجای
بعد از اجرای این دستور، پایتون شما را مجبور میکند به جای
نام این قابلیت از Barry Warsaw، یکی از توسعهدهندگان قدیمی پایتون، گرفته شده است.
البته کاربرد عملی خاصی ندارد و بیشتر یک شوخی تاریخی در دل زبان پایتون محسوب میشود. 😄
📚 منبع:
https://blog.imsadra.dev/10-python-easter-eggs
@Amin_TechLab
در پایتون، مقادیری که روی آنها عملیات انجام میشود را Operand و نماد انجامدهنده عملیات را Operator مینامیم.
برای مثال در کد زیر:
if x > y:
...
متغیرهای
x و y نقش Operand را دارند و علامت > یک Operator است.اما در میان اپراتورهای پایتون، موردی وجود دارد که خیلی از برنامهنویسها کمتر با آن آشنا هستند: Walrus Operator (
:=).این اپراتور علاوه بر انجام مقایسه یا ارزیابی یک عبارت، همزمان مقدار آن را در یک متغیر نیز ذخیره میکند.
مثلاً معمولاً کدی شبیه این مینویسیم:
length = len("test1234")
if length < 8:
print(f"Your password is {length} chars long.")اما با استفاده از Walrus Operator میتوان همان کار را به شکل خلاصهتر انجام داد:
if (length := len("test1234")) < 8:
print(f"Your password is {length} chars long.")نکته جالب اینجاست که فارغ از نتیجه شرط، متغیر
length پس از اجرای این دستور همچنان در دسترس خواهد بود.حالا سراغ یک اپراتور عجیبتر برویم؛ چیزی که احتمالاً هرگز در کدهای واقعی ندیدهاید!
در نسخههای قدیمی پایتون، برای عملگر «نامساوی» بهجای
!= از <> استفاده میشد. این سینتکس سالهاست منسوخ شده، اما هنوز بهعنوان یک ایستر اگ (Easter Egg) در پایتون وجود دارد.from __future__ import barry_as_FLUFL
بعد از اجرای این دستور، پایتون شما را مجبور میکند به جای
!= از <> استفاده کنید:>>> 2 != 3
SyntaxError: with Barry as BDFL, use '<>' instead of '!='
>>> 2 <> 3
True
نام این قابلیت از Barry Warsaw، یکی از توسعهدهندگان قدیمی پایتون، گرفته شده است.
البته کاربرد عملی خاصی ندارد و بیشتر یک شوخی تاریخی در دل زبان پایتون محسوب میشود. 😄
📚 منبع:
https://blog.imsadra.dev/10-python-easter-eggs
@Amin_TechLab
👾2❤1🔥1
🐍 چرا Generic Typing در پایتون اهمیت دارد؟
بسیاری از توسعهدهندگان هنگام استفاده از Type Hinting فقط سراغ انواع مشخصی مثل
به جای اینکه برای هر نوع داده یک تابع جداگانه تعریف کنید، میتوانید یک Type Variable بسازید و نوع ورودی و خروجی را به آن وابسته کنید. در این حالت ابزارهایی مانند MyPy و Pyright نیز میتوانند ناسازگاریهای نوعی را قبل از اجرای برنامه تشخیص دهند.
در مثال بالا، تابع
برای درک بهتر قدرت Genericها، به مثال زیر توجه کنید:
این تابع بسته به نوع دادهای که دریافت میکند، مقدار
حتی برای انواع تعریفشده توسط خودتان نیز همین اتفاق میافتد:
همانطور که میبینید، نوع
❓ چرا معمولاً از حرف
در واقع هیچ اجباری برای استفاده از
بنابراین کد زیر کاملاً معتبر است:
همچنین میتوانید از چند Generic Type بهطور همزمان استفاده کنید:
در این مثال، نوع
Generic Typing یکی از ابزارهای مهم برای نوشتن کدهای reusable، type-safe و قابل نگهداری در پروژههای بزرگ پایتونی است.
📚 منبع:
https://blog.imsadra.dev/generic-typing-in-python
@Amin_TechLab
بسیاری از توسعهدهندگان هنگام استفاده از Type Hinting فقط سراغ انواع مشخصی مثل
str، int یا list[str] میروند. اما وقتی یک تابع یا کلاس قرار است با چند نوع داده مختلف کار کند، Generic Typing به شما کمک میکند بدون از دست دادن Type Safety، کدی منعطفتر و قابلاستفادهتر بنویسید.به جای اینکه برای هر نوع داده یک تابع جداگانه تعریف کنید، میتوانید یک Type Variable بسازید و نوع ورودی و خروجی را به آن وابسته کنید. در این حالت ابزارهایی مانند MyPy و Pyright نیز میتوانند ناسازگاریهای نوعی را قبل از اجرای برنامه تشخیص دهند.
from typing import TypeVar
T = TypeVar("T")
def first(items: list[T]) -> T:
return items[0]
در مثال بالا، تابع
first میتواند لیستی از هر نوع دادهای را دریافت کند؛ اما نوع خروجی همیشه دقیقاً با نوع عناصر لیست یکسان خواهد بود.برای درک بهتر قدرت Genericها، به مثال زیر توجه کنید:
def select[T](items: list[T]) -> T:
...
این تابع بسته به نوع دادهای که دریافت میکند، مقدار
T را بهصورت خودکار تعیین میکند:select([1, 2, 3, 4, 5]) # T = int
select(["a", "b", "c"]) # T = str
select([1.2, 1.3, 1.4]) # T = float
حتی برای انواع تعریفشده توسط خودتان نیز همین اتفاق میافتد:
users = [User("Abby"), User("Chris"), User("Nick")]
select(users) # T = Userهمانطور که میبینید، نوع
T ثابت نیست و بر اساس نوع دادهای که به تابع ارسال میشود تعیین خواهد شد. این دقیقاً جوهره Generic Typing است.❓ چرا معمولاً از حرف
T استفاده میشود؟در واقع هیچ اجباری برای استفاده از
T وجود ندارد. این فقط یک قرارداد رایج بین برنامهنویسان است؛ درست مثل بسیاری از Conventionهای دیگر در دنیای توسعه نرمافزار.بنابراین کد زیر کاملاً معتبر است:
def duplicate[B](item: B) -> tuple[B, B, B]:
return (item, item, item)
همچنین میتوانید از چند Generic Type بهطور همزمان استفاده کنید:
def pair[A, B](first: A, second: B) -> tuple[A, B]:
return (first, second)
در این مثال، نوع
A و B مستقل از یکدیگر هستند و میتوانند هر نوع دادهای را نمایش دهند.Generic Typing یکی از ابزارهای مهم برای نوشتن کدهای reusable، type-safe و قابل نگهداری در پروژههای بزرگ پایتونی است.
📚 منبع:
https://blog.imsadra.dev/generic-typing-in-python
@Amin_TechLab
Sadra Yahyapour
Generic Typing in Python
Since Python introduced the ability to add generic types to functions, the language has become much more type-friendly and encourages you to follow this convention for more maintainable code. Obviously, this will lead to a better development experien...
🔥4
❌ پیام آخرین Commit رو اشتباه نوشتید؟
لازم نیست برای اصلاحش یک Commit جدید بسازید.
اگر میخواهید پیام آخرین Commit را تغییر دهید، میتوانید از Interactive Rebase استفاده کنید:
سپس در فایل بازشده، این خط را:
به این شکل تغییر دهید:
بعد از ذخیره و خروج، Git از شما میخواهد پیام جدید Commit را وارد کنید:
📌 دستورات مهم در Interactive Rebase:
💡 البته اگر فقط قصد دارید پیام آخرین Commit را تغییر دهید، معمولاً این دستور سریعتر و سادهتر است:
@Amin_TechLab
لازم نیست برای اصلاحش یک Commit جدید بسازید.
اگر میخواهید پیام آخرین Commit را تغییر دهید، میتوانید از Interactive Rebase استفاده کنید:
git rebase -i HEAD~1
سپس در فایل بازشده، این خط را:
pick abc123 Fix logni page
به این شکل تغییر دهید:
reword abc123 Fix logni page
بعد از ذخیره و خروج، Git از شما میخواهد پیام جدید Commit را وارد کنید:
Fix login page
📌 دستورات مهم در Interactive Rebase:
pick → استفاده از Commit بدون تغییرreword → تغییر پیام Commitedit → ویرایش Commitsquash → ادغام با Commit قبلیfixup → ادغام بدون نگه داشتن پیام Commitdrop → حذف Commit💡 البته اگر فقط قصد دارید پیام آخرین Commit را تغییر دهید، معمولاً این دستور سریعتر و سادهتر است:
git commit --amend -m "NEW MESSAGE"
@Amin_TechLab
👍2🙏2🔥1
💻 آشنایی با چند اپراتور کمتر شناختهشده در زبان C
در زبان C، مقادیری که روی آنها عملیات انجام میشود را Operand و نماد انجامدهنده عملیات را Operator مینامیم.
برای مثال در کد زیر:
متغیرهای
اما در میان اپراتورهای زبان C، چند مورد وجود دارد که شاید کمتر به آنها توجه کرده باشید.
یکی از آنها Comma Operator یا همان اپراتور ویرگول است.
در زبان C، ویرگول همیشه فقط برای جدا کردن آرگومانها یا متغیرها نیست؛ گاهی خودش یک اپراتور واقعی است.
این اپراتور چند عبارت را از چپ به راست اجرا میکند، اما مقدار نهایی آن برابر با مقدار آخرین عبارت است.
مثلاً:
در این کد، ابتدا دستور
یعنی بعد از اجرای کد بالا، مقدار
یک نمونه کاربردیتر:
در اینجا ابتدا مقدار
البته باید حواسمان باشد که استفاده زیاد از این اپراتور میتواند خوانایی کد را پایین بیاورد؛ برای همین معمولاً بهتر است فقط در جاهایی استفاده شود که واقعاً کد را تمیزتر میکند.
حالا سراغ یک مورد عجیبتر برویم؛ چیزی که خیلی از برنامهنویسهای C حتی اسمش را هم کمتر شنیدهاند: Digraphs.
در زبان C، برای بعضی از نمادها، شکلهای جایگزین وجود دارد.
مثلاً به جای
یعنی این کد:
میتواند به شکل زیر هم نوشته شود:
یا مثلاً به جای
که معادل این کد است:
این قابلیت در گذشته برای سیستمهایی کاربرد داشت که بعضی کاراکترهای خاص مثل
امروزه Digraphها تقریباً کاربرد عملی خاصی ندارند و بیشتر بهعنوان یک ویژگی تاریخی و عجیب در زبان C شناخته میشوند. 😄
@Amin_TechLab
در زبان C، مقادیری که روی آنها عملیات انجام میشود را Operand و نماد انجامدهنده عملیات را Operator مینامیم.
برای مثال در کد زیر:
if (x > y) {
...
}متغیرهای
x و y نقش Operand را دارند و علامت > یک Operator است.اما در میان اپراتورهای زبان C، چند مورد وجود دارد که شاید کمتر به آنها توجه کرده باشید.
یکی از آنها Comma Operator یا همان اپراتور ویرگول است.
در زبان C، ویرگول همیشه فقط برای جدا کردن آرگومانها یا متغیرها نیست؛ گاهی خودش یک اپراتور واقعی است.
این اپراتور چند عبارت را از چپ به راست اجرا میکند، اما مقدار نهایی آن برابر با مقدار آخرین عبارت است.
مثلاً:
int x;
x = (printf("Hello\n"), 10);
در این کد، ابتدا دستور
printf اجرا میشود و سپس مقدار 10 داخل متغیر x قرار میگیرد.یعنی بعد از اجرای کد بالا، مقدار
x برابر با 10 خواهد بود.یک نمونه کاربردیتر:
int i = 0;
int j = 0;
while (i < 5)
j += i, i++;
در اینجا ابتدا مقدار
i به j اضافه میشود و سپس i یک واحد افزایش پیدا میکند.البته باید حواسمان باشد که استفاده زیاد از این اپراتور میتواند خوانایی کد را پایین بیاورد؛ برای همین معمولاً بهتر است فقط در جاهایی استفاده شود که واقعاً کد را تمیزتر میکند.
حالا سراغ یک مورد عجیبتر برویم؛ چیزی که خیلی از برنامهنویسهای C حتی اسمش را هم کمتر شنیدهاند: Digraphs.
در زبان C، برای بعضی از نمادها، شکلهای جایگزین وجود دارد.
مثلاً به جای
{ و } میتوان از <% و %> استفاده کرد.یعنی این کد:
int main() {
return 0;
}میتواند به شکل زیر هم نوشته شود:
int main() <%
return 0;
%>
یا مثلاً به جای
[ و ] میتوان از <: و :> استفاده کرد:int arr<:3:> = <%1, 2, 3%>;
که معادل این کد است:
int arr[3] = {1, 2, 3};این قابلیت در گذشته برای سیستمهایی کاربرد داشت که بعضی کاراکترهای خاص مثل
{ یا [ روی کیبورد یا سیستمعاملشان بهراحتی در دسترس نبود.امروزه Digraphها تقریباً کاربرد عملی خاصی ندارند و بیشتر بهعنوان یک ویژگی تاریخی و عجیب در زبان C شناخته میشوند. 😄
@Amin_TechLab
❤2👍1🔥1
💻 یکی از خطرناکترین مفاهیم در زبان C: Undefined Behavior
در زبان C، همیشه اینطور نیست که هر کدی که کامپایل میشود، الزاماً رفتار مشخص و قابل اعتمادی داشته باشد.
گاهی برنامه از نظر ظاهری درست به نظر میرسد، کامپایلر هم هیچ خطایی نمیدهد، اما نتیجه اجرای برنامه میتواند کاملاً غیرقابل پیشبینی باشد.
به این حالت در زبان C میگوییم:
Undefined Behavior
یا به اختصار: UB
یعنی استاندارد زبان C هیچ تضمینی نمیدهد که برنامه دقیقاً چه رفتاری داشته باشد.
برای مثال به این کد دقت کنید:
شاید در نگاه اول فکر کنیم مقدار
اما مشکل اینجاست که در این عبارت، متغیر
نتیجه؟
ممکن است در یک کامپایلر عددی چاپ شود، در کامپایلر دیگر عددی متفاوت، و حتی با تغییر سطح optimization نتیجه عوض شود.
مثلاً اجرای برنامه با:
ممکن است یک خروجی بدهد، اما با:
خروجی متفاوتی ببینیم.
این یعنی برنامه وارد محدودهی Undefined Behavior شده است.
یک نمونه معروفتر:
اینجا ما در حال دسترسی به خانهای خارج از محدودهی آرایه هستیم.
زبان C جلوی ما را نمیگیرد، اما این به معنی درست بودن کد نیست.
ممکن است برنامه اجرا شود، ممکن است مقدار بیمعنی چاپ کند، ممکن است crash کند، یا حتی ظاهراً بدون مشکل کار کند.
و همین «ظاهراً بدون مشکل کار کردن» خطرناکترین بخش ماجراست.
در زبانهایی مثل Python یا Java معمولاً چنین خطاهایی سریعتر و واضحتر مشخص میشوند، اما در C مسئولیت زیادی روی دوش برنامهنویس است.
برای جلوگیری از Undefined Behavior بهتر است:
بهجای نوشتن عبارتهای پیچیده، تغییرات را مرحلهبهمرحله و خوانا بنویسیم.
زبان C بسیار سریع، قدرتمند و نزدیک به سختافزار است؛ اما همین قدرت باعث میشود کوچکترین بیدقتی، رفتار برنامه را غیرقابل پیشبینی کند.
پس در C فقط کامپایل شدن کد کافی نیست؛
باید مطمئن باشیم کد از نظر استاندارد زبان هم رفتار مشخصی دارد. ⚠️
@Amin_TechLab
در زبان C، همیشه اینطور نیست که هر کدی که کامپایل میشود، الزاماً رفتار مشخص و قابل اعتمادی داشته باشد.
گاهی برنامه از نظر ظاهری درست به نظر میرسد، کامپایلر هم هیچ خطایی نمیدهد، اما نتیجه اجرای برنامه میتواند کاملاً غیرقابل پیشبینی باشد.
به این حالت در زبان C میگوییم:
Undefined Behavior
یا به اختصار: UB
یعنی استاندارد زبان C هیچ تضمینی نمیدهد که برنامه دقیقاً چه رفتاری داشته باشد.
برای مثال به این کد دقت کنید:
#include <stdio.h>
int main() {
int x = 5;
int y = x++ + ++x;
printf("%d\n", y);
return 0;
}
شاید در نگاه اول فکر کنیم مقدار
y قابل محاسبه است.اما مشکل اینجاست که در این عبارت، متغیر
x بیش از یک بار بین دو نقطهی مشخص از اجرای برنامه تغییر کرده است و ترتیب دقیق این تغییرات تضمینشده نیست.نتیجه؟
ممکن است در یک کامپایلر عددی چاپ شود، در کامپایلر دیگر عددی متفاوت، و حتی با تغییر سطح optimization نتیجه عوض شود.
مثلاً اجرای برنامه با:
gcc main.c
ممکن است یک خروجی بدهد، اما با:
gcc main.c -O2
خروجی متفاوتی ببینیم.
این یعنی برنامه وارد محدودهی Undefined Behavior شده است.
یک نمونه معروفتر:
int arr[3] = {1, 2, 3};
printf("%d\n", arr[5]);اینجا ما در حال دسترسی به خانهای خارج از محدودهی آرایه هستیم.
زبان C جلوی ما را نمیگیرد، اما این به معنی درست بودن کد نیست.
ممکن است برنامه اجرا شود، ممکن است مقدار بیمعنی چاپ کند، ممکن است crash کند، یا حتی ظاهراً بدون مشکل کار کند.
و همین «ظاهراً بدون مشکل کار کردن» خطرناکترین بخش ماجراست.
در زبانهایی مثل Python یا Java معمولاً چنین خطاهایی سریعتر و واضحتر مشخص میشوند، اما در C مسئولیت زیادی روی دوش برنامهنویس است.
برای جلوگیری از Undefined Behavior بهتر است:
int x = 5;
x++;
int y = x;
x++;
y += x;
بهجای نوشتن عبارتهای پیچیده، تغییرات را مرحلهبهمرحله و خوانا بنویسیم.
زبان C بسیار سریع، قدرتمند و نزدیک به سختافزار است؛ اما همین قدرت باعث میشود کوچکترین بیدقتی، رفتار برنامه را غیرقابل پیشبینی کند.
پس در C فقط کامپایل شدن کد کافی نیست؛
باید مطمئن باشیم کد از نظر استاندارد زبان هم رفتار مشخصی دارد. ⚠️
@Amin_TechLab
❤1🔥1👾1
🐍 سه نقطه
شاید در بعضی از کدهای پایتون چیزی شبیه این دیده باشید:
در نگاه اول ممکن است فکر کنیم این سه نقطه فقط برای خالی گذاشتن بدنه تابع استفاده شده یا چیزی شبیه
در واقع
یعنی سه نقطه در پایتون نه کامنت است، نه صرفاً یک علامت نمایشی، بلکه یک آبجکت واقعی از نوع
اما سؤال اصلی اینجاست:
آیا
نه دقیقاً.
مثلاً:
اینجا
«فعلاً این بخش خالی است، ولی از نظر syntax مشکلی ندارد.»
اما
خروجی:
پس تفاوت مهم اینجاست:
هیچ مقداری تولید نمیکند.
ولی:
یک آبجکت واقعی به نام
یکی از کاربردهای رایج
این روش بهخصوص در نوشتن اسکلت اولیه کلاسها، توابع، اینترفیسها یا کدهایی که قرار است بعداً تکمیل شوند، کاربرد دارد.
البته از نظر خوانایی، اگر فقط میخواهید بگویید «اینجا هنوز کاری انجام نمیشود»، استفاده از
اما
مثلاً:
در اینجا
همچنین در type hinting و stub fileها هم زیاد دیده میشود؛ مخصوصاً در فایلهایی که فقط ساختار تابع یا کلاس را مشخص میکنند، اما پیادهسازی واقعی ندارند.
مثلاً:
در این حالت،
پس دفعه بعد که در کد پایتون با
@Amin_TechLab
... در پایتون فقط برای زیبایی نیست!شاید در بعضی از کدهای پایتون چیزی شبیه این دیده باشید:
def process_data():
...
در نگاه اول ممکن است فکر کنیم این سه نقطه فقط برای خالی گذاشتن بدنه تابع استفاده شده یا چیزی شبیه
pass است؛ اما در پایتون، ... یک مفهوم واقعی دارد.در واقع
... همان آبجکتی به نام Ellipsis است.>>> ...
Ellipsis
>>> type(...)
<class 'ellipsis'>
یعنی سه نقطه در پایتون نه کامنت است، نه صرفاً یک علامت نمایشی، بلکه یک آبجکت واقعی از نوع
ellipsis محسوب میشود.اما سؤال اصلی اینجاست:
آیا
... همان pass است؟نه دقیقاً.
pass یک statement خالی است. یعنی وقتی پایتون به آن میرسد، هیچ کاری انجام نمیدهد و فقط اجازه میدهد ساختار کد از نظر نحوی معتبر باقی بماند.مثلاً:
def process_data():
pass
اینجا
pass فقط میگوید:«فعلاً این بخش خالی است، ولی از نظر syntax مشکلی ندارد.»
اما
... یک expression است، یعنی خودش یک مقدار واقعی تولید میکند:x = ...
print(x)
خروجی:
Ellipsis
پس تفاوت مهم اینجاست:
pass
هیچ مقداری تولید نمیکند.
ولی:
...
یک آبجکت واقعی به نام
Ellipsis است.یکی از کاربردهای رایج
... این است که در زمان طراحی اولیه کد، جای بخشهایی را که هنوز پیادهسازی نشدهاند نگه داریم:class UserService:
def create_user(self):
...
def delete_user(self):
...
این روش بهخصوص در نوشتن اسکلت اولیه کلاسها، توابع، اینترفیسها یا کدهایی که قرار است بعداً تکمیل شوند، کاربرد دارد.
البته از نظر خوانایی، اگر فقط میخواهید بگویید «اینجا هنوز کاری انجام نمیشود»، استفاده از
pass همچنان رایجتر و واضحتر است.اما
Ellipsis فقط برای خالی گذاشتن تابع نیست. در بعضی کتابخانهها، مخصوصاً در حوزههایی مثل NumPy، از ... برای indexing و slicing پیشرفته استفاده میشود.مثلاً:
array[..., 0]
در اینجا
... معنای خاصی دارد و به کتابخانه کمک میکند با ابعاد مختلف آرایهها راحتتر کار کند.همچنین در type hinting و stub fileها هم زیاد دیده میشود؛ مخصوصاً در فایلهایی که فقط ساختار تابع یا کلاس را مشخص میکنند، اما پیادهسازی واقعی ندارند.
مثلاً:
def connect(host: str, port: int) -> bool: ...
در این حالت،
... یعنی امضای تابع مشخص شده، اما بدنه آن در جای دیگری تعریف میشود یا فعلاً اهمیتی ندارد.پس دفعه بعد که در کد پایتون با
... روبهرو شدید، بدانید که این سه نقطه فقط یک جایخالی ساده نیست.... در پایتون همان Ellipsis است؛ یک آبجکت واقعی، با کاربردهایی فراتر از چیزی که در نگاه اول به نظر میرسد.@Amin_TechLab
👾3👍2
🚀 قسمت ۱: افزایش عملکرد CPU در لینوکس با
اگر از لینوکس استفاده میکنید و میخواهید در کارهای سنگین، عملکرد پردازنده را افزایش دهید، یکی از ابزارهای کاربردی برای این کار
این ابزار به شما اجازه میدهد حالت کاری CPU را تغییر دهید و مشخص کنید پردازنده بیشتر روی مصرف انرژی تمرکز کند یا روی حداکثر عملکرد.
در لینوکس، CPU همیشه با یک فرکانس ثابت کار نمیکند. سیستمعامل با توجه به میزان فشار کاری، دما، مصرف برق و تنظیمات مدیریت انرژی، فرکانس پردازنده را کم یا زیاد میکند.
این رفتار توسط چیزی به نام CPU Governor کنترل میشود.
Governor در واقع سیاست کاری پردازنده را مشخص میکند.
برای مثال:
مصرف انرژی را کاهش میدهد و معمولاً CPU را در فرکانس پایینتری نگه میدارد.
فرکانس پردازنده را با توجه به نیاز سیستم کم و زیاد میکند.
در سیستمهای جدیدتر رایج است و تلاش میکند بین مصرف انرژی و عملکرد تعادل ایجاد کند.
پردازنده را در حالت حداکثر عملکرد قرار میدهد.
اگر در حال کامپایل پروژههای بزرگ، اجرای ماشین مجازی، بازی، رندر، پردازش داده یا بنچمارک هستید، حالت
اما باید توجه داشت که این حالت معمولاً مصرف انرژی و دمای CPU را هم افزایش میدهد.
پس اگر از لپتاپ استفاده میکنید، بهتر است فقط زمانی که واقعاً به قدرت پردازشی بالا نیاز دارید، این حالت را فعال کنید.
ادامه دارد...
@Amin_TechLab
cpupowerاگر از لینوکس استفاده میکنید و میخواهید در کارهای سنگین، عملکرد پردازنده را افزایش دهید، یکی از ابزارهای کاربردی برای این کار
cpupower است.این ابزار به شما اجازه میدهد حالت کاری CPU را تغییر دهید و مشخص کنید پردازنده بیشتر روی مصرف انرژی تمرکز کند یا روی حداکثر عملکرد.
در لینوکس، CPU همیشه با یک فرکانس ثابت کار نمیکند. سیستمعامل با توجه به میزان فشار کاری، دما، مصرف برق و تنظیمات مدیریت انرژی، فرکانس پردازنده را کم یا زیاد میکند.
این رفتار توسط چیزی به نام CPU Governor کنترل میشود.
Governor در واقع سیاست کاری پردازنده را مشخص میکند.
برای مثال:
powersaveمصرف انرژی را کاهش میدهد و معمولاً CPU را در فرکانس پایینتری نگه میدارد.
ondemandفرکانس پردازنده را با توجه به نیاز سیستم کم و زیاد میکند.
schedutilدر سیستمهای جدیدتر رایج است و تلاش میکند بین مصرف انرژی و عملکرد تعادل ایجاد کند.
performanceپردازنده را در حالت حداکثر عملکرد قرار میدهد.
اگر در حال کامپایل پروژههای بزرگ، اجرای ماشین مجازی، بازی، رندر، پردازش داده یا بنچمارک هستید، حالت
performance میتواند باعث افزایش سرعت پاسخدهی سیستم شود.اما باید توجه داشت که این حالت معمولاً مصرف انرژی و دمای CPU را هم افزایش میدهد.
پس اگر از لپتاپ استفاده میکنید، بهتر است فقط زمانی که واقعاً به قدرت پردازشی بالا نیاز دارید، این حالت را فعال کنید.
ادامه دارد...
@Amin_TechLab
👍2🔥2
تا امروز خیلی از ما از ابزارهای AI مثل Claude Code، Cursor یا Codex شبیه یک چتبات استفاده میکردیم:
Prompt مینوشتیم، خروجی میگرفتیم، ایرادها را اصلاح میکردیم و دوباره Prompt جدید میدادیم.
اما قدم بعدی احتمالاً چیزی فراتر از Prompt Engineering است:
🔥 Loop Engineering
در این رویکرد، بهجای نوشتن Promptهای بهتر، یک چرخه کاری طراحی میشود که Agentها بتوانند داخل آن کار را جلو ببرند.
Agent میتواند تسکها را بررسی کند، کد را تغییر دهد، تستها را اجرا کند، خطاها را اصلاح کند، PRها و Issueها را مدیریت کند و فقط وقتی تصمیم انسانی لازم بود، کار را به انسان ارجاع دهد.
به زبان ساده، مسیر از این مدل:
«من Prompt مینویسم، AI جواب میدهد»
به این مدل تغییر میکند:
«من سیستم را طراحی میکنم، Agentها کار را جلو میبرند.»
در این مدل، نقش برنامهنویس حذف نمیشود؛ بلکه به طراحی سیستم، تعیین محدودیتها و کنترل کیفیت تغییر میکند.
بهنظرم Loop Engineering یکی از جذابترین اتفاقات این روزهای AI برای برنامهنویسهاست.
github.com/cobusgreyling/loop-engineering
@Amin_TechLab
Prompt مینوشتیم، خروجی میگرفتیم، ایرادها را اصلاح میکردیم و دوباره Prompt جدید میدادیم.
اما قدم بعدی احتمالاً چیزی فراتر از Prompt Engineering است:
🔥 Loop Engineering
در این رویکرد، بهجای نوشتن Promptهای بهتر، یک چرخه کاری طراحی میشود که Agentها بتوانند داخل آن کار را جلو ببرند.
Agent میتواند تسکها را بررسی کند، کد را تغییر دهد، تستها را اجرا کند، خطاها را اصلاح کند، PRها و Issueها را مدیریت کند و فقط وقتی تصمیم انسانی لازم بود، کار را به انسان ارجاع دهد.
به زبان ساده، مسیر از این مدل:
«من Prompt مینویسم، AI جواب میدهد»
به این مدل تغییر میکند:
«من سیستم را طراحی میکنم، Agentها کار را جلو میبرند.»
در این مدل، نقش برنامهنویس حذف نمیشود؛ بلکه به طراحی سیستم، تعیین محدودیتها و کنترل کیفیت تغییر میکند.
بهنظرم Loop Engineering یکی از جذابترین اتفاقات این روزهای AI برای برنامهنویسهاست.
github.com/cobusgreyling/loop-engineering
@Amin_TechLab
👍5❤1
Amin'sTechLab
🚀 قسمت ۱: افزایش عملکرد CPU در لینوکس با cpupower اگر از لینوکس استفاده میکنید و میخواهید در کارهای سنگین، عملکرد پردازنده را افزایش دهید، یکی از ابزارهای کاربردی برای این کار cpupower است. این ابزار به شما اجازه میدهد حالت کاری CPU را تغییر دهید و مشخص…
⚙️ قسمت ۲: نصب و بررسی وضعیت CPU با
برای استفاده از ابزار
📦 در اوبونتو، دبیان و توزیعهای مبتنی بر آنها:
در بعضی نسخههای اوبونتو ممکن است لازم باشد ابزار مخصوص کرنل فعلی را هم نصب کنید:
📦 در فدورا:
📦 در Red Hat / RHEL / CentOS:
یا در نسخههای جدیدتر:
📦 در آرچ لینوکس و توزیعهای مبتنی بر آن:
بعد از نصب، بهتر است قبل از تغییر تنظیمات، وضعیت فعلی پردازنده را بررسی کنید:
این دستور اطلاعات مهمی درباره CPU نمایش میدهد؛ از جمله:
حداقل و حداکثر فرکانس پردازنده
Governor فعال فعلی
درایور مدیریت فرکانس CPU
حالتهای قابل استفاده برای مدیریت مصرف انرژی
محدوده فرکانسی قابل پشتیبانی توسط پردازنده
در خروجی این دستور معمولاً بخشی با عنوان available cpufreq governors وجود دارد. این بخش نشان میدهد سیستم شما از چه حالتهایی مثل
بررسی این اطلاعات قبل از اعمال تغییرات مهم است، چون همه سیستمها دقیقاً گزینههای یکسانی ندارند.
ادامه دارد...
@Amin_TechLab
cpupowerبرای استفاده از ابزار
cpupower ابتدا باید بسته مربوط به آن را نصب کنید. نام بسته در توزیعهای مختلف لینوکس ممکن است متفاوت باشد.📦 در اوبونتو، دبیان و توزیعهای مبتنی بر آنها:
sudo apt update
sudo apt install linux-tools-common
در بعضی نسخههای اوبونتو ممکن است لازم باشد ابزار مخصوص کرنل فعلی را هم نصب کنید:
sudo apt install linux-tools-$(uname -r)
📦 در فدورا:
sudo dnf install kernel-tools
📦 در Red Hat / RHEL / CentOS:
sudo yum install kernel-tools
یا در نسخههای جدیدتر:
sudo dnf install kernel-tools
📦 در آرچ لینوکس و توزیعهای مبتنی بر آن:
sudo pacman -S cpupower
بعد از نصب، بهتر است قبل از تغییر تنظیمات، وضعیت فعلی پردازنده را بررسی کنید:
cpupower frequency-info
این دستور اطلاعات مهمی درباره CPU نمایش میدهد؛ از جمله:
حداقل و حداکثر فرکانس پردازنده
Governor فعال فعلی
درایور مدیریت فرکانس CPU
حالتهای قابل استفاده برای مدیریت مصرف انرژی
محدوده فرکانسی قابل پشتیبانی توسط پردازنده
در خروجی این دستور معمولاً بخشی با عنوان available cpufreq governors وجود دارد. این بخش نشان میدهد سیستم شما از چه حالتهایی مثل
performance، powersave، ondemand یا schedutil پشتیبانی میکند.بررسی این اطلاعات قبل از اعمال تغییرات مهم است، چون همه سیستمها دقیقاً گزینههای یکسانی ندارند.
ادامه دارد...
@Amin_TechLab
👾2❤1👍1🔥1
Amin'sTechLab
⚙️ قسمت ۲: نصب و بررسی وضعیت CPU با cpupower برای استفاده از ابزار cpupower ابتدا باید بسته مربوط به آن را نصب کنید. نام بسته در توزیعهای مختلف لینوکس ممکن است متفاوت باشد. 📦 در اوبونتو، دبیان و توزیعهای مبتنی بر آنها: sudo apt update sudo apt install linux…
🔥 قسمت ۳: فعالسازی حالت Performance برای CPU در لینوکس
بعد از نصب cpupower، میتوانید با یک دستور ساده CPU را روی حالت حداکثر عملکرد قرار دهید:
با اجرای این دستور، Governor پردازنده روی حالت performance تنظیم میشود.
در این حالت، سیستم تلاش میکند پردازنده را در سطح بالاتری از توان پردازشی نگه دارد تا برنامهها سریعتر پاسخ دهند و پردازشهای سنگین با تأخیر کمتری انجام شوند.
این حالت برای موقعیتهایی مثل موارد زیر مناسب است:
کامپایل پروژههای سنگین
رندر گرفتن
اجرای ماشین مجازی
پردازش داده
اجرای بازی
تست و بنچمارک سیستم
اجرای سرویسهای حساس به تأخیر
اما حالت performance همیشه بهترین انتخاب نیست.
اگر از لپتاپ استفاده میکنید، فعال بودن دائمی این حالت میتواند باعث افزایش مصرف باتری، بالا رفتن دمای CPU و بیشتر شدن صدای فن شود.
برای برگشت به حالت کممصرفتر میتوانید از دستور زیر استفاده کنید:
اگر سیستم شما از schedutil پشتیبانی میکند، این حالت معمولاً انتخاب متعادلی بین عملکرد و مصرف انرژی است:
جمعبندی ساده:
performance یعنی قدرت بیشتر، مصرف بیشتر و دمای بالاتر.
powersave یعنی مصرف کمتر، عملکرد محدودتر و شارژدهی بهتر.
schedutil یا ondemand یعنی تعادل بین سرعت و مصرف انرژی.
پس اگر به حداکثر توان CPU نیاز دارید، performance گزینه مناسبی است؛ اما برای استفاده روزمره، حالتهای متعادل معمولاً انتخاب بهتری هستند.
@Amin_TechLab
بعد از نصب cpupower، میتوانید با یک دستور ساده CPU را روی حالت حداکثر عملکرد قرار دهید:
sudo cpupower frequency-set -g performance
با اجرای این دستور، Governor پردازنده روی حالت performance تنظیم میشود.
در این حالت، سیستم تلاش میکند پردازنده را در سطح بالاتری از توان پردازشی نگه دارد تا برنامهها سریعتر پاسخ دهند و پردازشهای سنگین با تأخیر کمتری انجام شوند.
این حالت برای موقعیتهایی مثل موارد زیر مناسب است:
کامپایل پروژههای سنگین
رندر گرفتن
اجرای ماشین مجازی
پردازش داده
اجرای بازی
تست و بنچمارک سیستم
اجرای سرویسهای حساس به تأخیر
اما حالت performance همیشه بهترین انتخاب نیست.
اگر از لپتاپ استفاده میکنید، فعال بودن دائمی این حالت میتواند باعث افزایش مصرف باتری، بالا رفتن دمای CPU و بیشتر شدن صدای فن شود.
برای برگشت به حالت کممصرفتر میتوانید از دستور زیر استفاده کنید:
sudo cpupower frequency-set -g powersave
اگر سیستم شما از schedutil پشتیبانی میکند، این حالت معمولاً انتخاب متعادلی بین عملکرد و مصرف انرژی است:
sudo cpupower frequency-set -g schedutil
جمعبندی ساده:
performance یعنی قدرت بیشتر، مصرف بیشتر و دمای بالاتر.
powersave یعنی مصرف کمتر، عملکرد محدودتر و شارژدهی بهتر.
schedutil یا ondemand یعنی تعادل بین سرعت و مصرف انرژی.
پس اگر به حداکثر توان CPU نیاز دارید، performance گزینه مناسبی است؛ اما برای استفاده روزمره، حالتهای متعادل معمولاً انتخاب بهتری هستند.
@Amin_TechLab
👾3👍2❤1🔥1
💻 یکی از قوانین کمتر شناختهشده اما بسیار مهم در زبان C: Strict Aliasing Rule
یکی از رایجترین اشتباهاتی که حتی برنامهنویسهای باتجربه C مرتکب میشوند، تبدیل مستقیم یک اشارهگر به نوعی دیگر و دسترسی به داده از طریق آن است.
برای مثال:
در نگاه اول شاید تصور کنیم فقط داریم بیتهای متغیر
اما مشکل اینجاست که طبق استاندارد زبان C، یک شیء باید معمولاً فقط از طریق اشارهگری از همان نوع خودش (یا چند استثنای مشخص) خوانده یا نوشته شود.
به این قانون میگویند:
Strict Aliasing Rule
کامپایلرها هنگام فعال بودن Optimization فرض میکنند که اشارهگرهای با نوعهای متفاوت، هرگز به یک محل از حافظه اشاره نمیکنند.
به همین دلیل ممکن است کامپایلر کدی تولید کند که اصلاً انتظارش را ندارید.
مثلاً این کد را ببینید:
اگر
اما کامپایلر مجاز است فرض کند این دو اشارهگر هرگز به یک داده اشاره نمیکنند و بر همین اساس کد را بهینه کند.
در نتیجه، خروجی برنامه میتواند کاملاً با انتظار شما متفاوت باشد.
نکته جالب اینجاست که ممکن است برنامه:
کاملاً درست کار کند.
اما فقط با فعال کردن Optimization:
رفتار برنامه تغییر کند.
دلیلش این نیست که Optimization خراب است؛
بلکه برنامه از ابتدا قوانین استاندارد C را نقض کرده است.
اگر هدفتان مشاهده بیتهای یک متغیر است، روش صحیح استفاده از
یا در صورت نیاز، از
چرا این قانون وجود دارد؟
کامپایلر با دانستن اینکه انواع مختلف روی یک حافظه قرار ندارند، میتواند:
دادهها را داخل Register نگه دارد.
دسترسیهای غیرضروری به حافظه را حذف کند.
کد بسیار سریعتری تولید کند.
به همین دلیل است که نقض این قانون معمولاً فقط هنگام Optimization خودش را نشان میدهد.
در زبان C، هر Castی مجاز نیست؛
گاهی یک Cast ساده میتواند باعث شود کامپایلر فرضهایی انجام دهد که نتیجه برنامه را کاملاً تغییر دهد.
پس اگر برنامهای بدون Optimization درست کار میکند اما با
@Amin_TechLab
یکی از رایجترین اشتباهاتی که حتی برنامهنویسهای باتجربه C مرتکب میشوند، تبدیل مستقیم یک اشارهگر به نوعی دیگر و دسترسی به داده از طریق آن است.
برای مثال:
#include <stdio.h>
int main() {
float f = 3.14f;
int *p = (int *)&f;
printf("%x\n", *p);
return 0;
}
در نگاه اول شاید تصور کنیم فقط داریم بیتهای متغیر
float را به صورت یک عدد صحیح مشاهده میکنیم.اما مشکل اینجاست که طبق استاندارد زبان C، یک شیء باید معمولاً فقط از طریق اشارهگری از همان نوع خودش (یا چند استثنای مشخص) خوانده یا نوشته شود.
به این قانون میگویند:
Strict Aliasing Rule
کامپایلرها هنگام فعال بودن Optimization فرض میکنند که اشارهگرهای با نوعهای متفاوت، هرگز به یک محل از حافظه اشاره نمیکنند.
به همین دلیل ممکن است کامپایلر کدی تولید کند که اصلاً انتظارش را ندارید.
مثلاً این کد را ببینید:
int foo(float *f, int *i)
{
*f = 0.0f;
*i = 42;
return *f == 0.0f;
}
اگر
f و i در واقع به یک آدرس اشاره کنند (با Cast کردن)، ممکن است انتظار داشته باشید نتیجه تابع تغییر کند.اما کامپایلر مجاز است فرض کند این دو اشارهگر هرگز به یک داده اشاره نمیکنند و بر همین اساس کد را بهینه کند.
در نتیجه، خروجی برنامه میتواند کاملاً با انتظار شما متفاوت باشد.
نکته جالب اینجاست که ممکن است برنامه:
gcc main.c
کاملاً درست کار کند.
اما فقط با فعال کردن Optimization:
gcc main.c -O2
رفتار برنامه تغییر کند.
دلیلش این نیست که Optimization خراب است؛
بلکه برنامه از ابتدا قوانین استاندارد C را نقض کرده است.
اگر هدفتان مشاهده بیتهای یک متغیر است، روش صحیح استفاده از
memcpy است:#include <string.h>
float f = 3.14f;
int bits;
memcpy(&bits, &f, sizeof(bits));
یا در صورت نیاز، از
unsigned char * برای دسترسی سطح بایت استفاده کنید؛ زیرا استاندارد C این مورد را مجاز میداند.چرا این قانون وجود دارد؟
کامپایلر با دانستن اینکه انواع مختلف روی یک حافظه قرار ندارند، میتواند:
دادهها را داخل Register نگه دارد.
دسترسیهای غیرضروری به حافظه را حذف کند.
کد بسیار سریعتری تولید کند.
به همین دلیل است که نقض این قانون معمولاً فقط هنگام Optimization خودش را نشان میدهد.
در زبان C، هر Castی مجاز نیست؛
گاهی یک Cast ساده میتواند باعث شود کامپایلر فرضهایی انجام دهد که نتیجه برنامه را کاملاً تغییر دهد.
پس اگر برنامهای بدون Optimization درست کار میکند اما با
-O2 رفتار عجیبی پیدا میکند، یکی از اولین چیزهایی که باید بررسی کنید، نقض Strict Aliasing Rule است. ⚠️@Amin_TechLab
❤4👍3👾2
💻 یکی از اشتباهات رایج در زبان C: برگرداندن آدرس یک متغیر Local از تابع (بخش اول)
یکی از باگهای خطرناک در زبان C، استفاده از اشارهگری است که به یک متغیر محلی (Local Variable) اشاره میکند.
مثلاً:
در نگاه اول شاید تصور کنیم تابع:
آدرس متغیر
اما یک مشکل مهم وجود دارد.
متغیر:
یک متغیر Local است.
یعنی فقط در زمان اجرای تابع:
وجود دارد.
وقتی تابع تمام میشود، فضای مربوط به آن در Stack دیگر معتبر نیست.
پس اشارهگری که برگردانده شده:
به حافظهای اشاره میکند که دیگر مالکیتی روی آن نداریم.
به این حالت میگویند:
Dangling Pointer
ممکن است این کد در بعضی سیستمها خروجی زیر را بدهد:
و فکر کنید مشکلی وجود ندارد.
اما این فقط یک اتفاق تصادفی است.
در واقع برنامه شما وارد محدودهای شده که استاندارد C هیچ رفتاری را تضمین نمیکند.
در بخش بعدی میبینیم چرا این نوع باگها گاهی بدون خطا اجرا میشوند و بعد از تغییر یک خط ساده یا فعال کردن Optimization خراب میشوند.
@Amin_TechLab
یکی از باگهای خطرناک در زبان C، استفاده از اشارهگری است که به یک متغیر محلی (Local Variable) اشاره میکند.
مثلاً:
#include <stdio.h>
int *getNumber()
{
int x = 42;
return &x;
}
int main()
{
int *ptr = getNumber();
printf("%d\n", *ptr);
return 0;
}
در نگاه اول شاید تصور کنیم تابع:
return &x;
آدرس متغیر
x را برمیگرداند و در main میتوانیم مقدار آن را بخوانیم.اما یک مشکل مهم وجود دارد.
متغیر:
int x = 42;
یک متغیر Local است.
یعنی فقط در زمان اجرای تابع:
getNumber()
وجود دارد.
وقتی تابع تمام میشود، فضای مربوط به آن در Stack دیگر معتبر نیست.
پس اشارهگری که برگردانده شده:
ptr
به حافظهای اشاره میکند که دیگر مالکیتی روی آن نداریم.
به این حالت میگویند:
Dangling Pointer
ممکن است این کد در بعضی سیستمها خروجی زیر را بدهد:
42
و فکر کنید مشکلی وجود ندارد.
اما این فقط یک اتفاق تصادفی است.
در واقع برنامه شما وارد محدودهای شده که استاندارد C هیچ رفتاری را تضمین نمیکند.
در بخش بعدی میبینیم چرا این نوع باگها گاهی بدون خطا اجرا میشوند و بعد از تغییر یک خط ساده یا فعال کردن Optimization خراب میشوند.
@Amin_TechLab
❤3🔥3👾2
Amin'sTechLab
💻 یکی از اشتباهات رایج در زبان C: برگرداندن آدرس یک متغیر Local از تابع (بخش اول) یکی از باگهای خطرناک در زبان C، استفاده از اشارهگری است که به یک متغیر محلی (Local Variable) اشاره میکند. مثلاً: #include <stdio.h> int *getNumber() { int x = 42; …
💻 چرا Dangling Pointerها در C خیلی خطرناک هستند؟ (بخش دوم)
یکی از دلایلی که این نوع خطاها سخت پیدا میشوند این است که برنامه همیشه Crash نمیکند.
مثلاً:
این کد اشتباه است.
چون آرایه:
داخل Stack ساخته میشود.
بعد از خروج از تابع:
این حافظه دیگر معتبر نیست.
اما اشارهگر هنوز همان آدرس قبلی را نگه داشته است.
حالا ممکن است:
گاهی درست کار کند.
گاهی خروجی خراب بدهد:
و گاهی برنامه Crash کند.
حتی تغییرهای ساده میتوانند رفتار برنامه را تغییر دهند:
اضافه کردن همین تابع ممکن است باعث شود حافظه قبلی Stack دوباره استفاده شود و مقدار اشارهگر شما تغییر کند.
مشکل از کامپایلر نیست؛
برنامه شما دارد از حافظهای استفاده میکند که دیگر وجود ندارد.
در بخش آخر روشهای صحیح مدیریت این شرایط را بررسی میکنیم.
@Amin_TechLab
یکی از دلایلی که این نوع خطاها سخت پیدا میشوند این است که برنامه همیشه Crash نمیکند.
مثلاً:
char *getName()
{
char name[] = "Embedded";
return name;
}
این کد اشتباه است.
چون آرایه:
char name[]
داخل Stack ساخته میشود.
بعد از خروج از تابع:
getName()
|
v
+---------------+
| name[] |
+---------------+
این حافظه دیگر معتبر نیست.
اما اشارهگر هنوز همان آدرس قبلی را نگه داشته است.
حالا ممکن است:
printf("%s", getName());گاهی درست کار کند.
گاهی خروجی خراب بدهد:
Embe@#12
و گاهی برنامه Crash کند.
حتی تغییرهای ساده میتوانند رفتار برنامه را تغییر دهند:
void test()
{
int buffer[100];
}
اضافه کردن همین تابع ممکن است باعث شود حافظه قبلی Stack دوباره استفاده شود و مقدار اشارهگر شما تغییر کند.
مشکل از کامپایلر نیست؛
برنامه شما دارد از حافظهای استفاده میکند که دیگر وجود ندارد.
در بخش آخر روشهای صحیح مدیریت این شرایط را بررسی میکنیم.
@Amin_TechLab
❤2🔥2👾2
💻 روش صحیح مدیریت طول عمر حافظه در زبان C (بخش سوم)
اگر میخواهید مقداری بعد از خروج از تابع باقی بماند، نباید آدرس یک متغیر Local را برگردانید.
چند روش صحیح وجود دارد:
✅ روش اول: استفاده از
متغیرهای
✅ روش دوم: استفاده از حافظه Dynamic
اما مسئولیت آزاد کردن حافظه با شماست:
اگر این کار انجام نشود، مشکل دیگری به نام:
Memory Leak
ایجاد میشود.
✅ روش سوم: ارسال Buffer توسط Caller
این روش در برنامههای Embedded بسیار رایج است:
در این حالت کسی که حافظه را ساخته، طول عمر آن را هم کنترل میکند.
در زبان C همیشه یک سؤال مهم وجود دارد:
این حافظه متعلق به چه کسی است و تا چه زمانی معتبر است؟
بسیاری از خطاهای سخت در Embedded و سیستمهای Low-Level به دلیل پاسخ اشتباه به همین سؤال ایجاد میشوند.
یک Pointer فقط یک آدرس نیست؛
یک قرارداد درباره مالکیت و طول عمر حافظه است. ⚠️
@Amin_TechLab
اگر میخواهید مقداری بعد از خروج از تابع باقی بماند، نباید آدرس یک متغیر Local را برگردانید.
چند روش صحیح وجود دارد:
✅ روش اول: استفاده از
staticchar *getName()
{
static char name[] = "Embedded";
return name;
}
متغیرهای
static تا پایان اجرای برنامه در حافظه باقی میمانند.✅ روش دوم: استفاده از حافظه Dynamic
#include <stdlib.h>
#include <string.h>
char *getName()
{
char *name = malloc(20);
strcpy(name, "Embedded");
return name;
}
اما مسئولیت آزاد کردن حافظه با شماست:
free(name);
اگر این کار انجام نشود، مشکل دیگری به نام:
Memory Leak
ایجاد میشود.
✅ روش سوم: ارسال Buffer توسط Caller
این روش در برنامههای Embedded بسیار رایج است:
void getName(char *buffer)
{
strcpy(buffer, "Embedded");
}
در این حالت کسی که حافظه را ساخته، طول عمر آن را هم کنترل میکند.
در زبان C همیشه یک سؤال مهم وجود دارد:
این حافظه متعلق به چه کسی است و تا چه زمانی معتبر است؟
بسیاری از خطاهای سخت در Embedded و سیستمهای Low-Level به دلیل پاسخ اشتباه به همین سؤال ایجاد میشوند.
یک Pointer فقط یک آدرس نیست؛
یک قرارداد درباره مالکیت و طول عمر حافظه است. ⚠️
@Amin_TechLab
🔥3👾3❤2👍1
پشت صحنهی یک دستور در لینوکس! (۱/۱۰)
وقتی در لینوکس ترمینال را باز میکنید و یک دستور مثل
قبل از اینکه سراغ شل برویم، باید کمی به گذشته برگردیم.
دهههای ۱۹۵۰ و ۱۹۶۰ چیزی به اسم «ترمینال» اصلاً یک پنجرهی نرمافزاری نبود؛ بلکه یک دستگاه فیزیکی بود که تنها وظیفهاش دریافت ورودی از کاربر و نمایش خروجی کامپیوتر بود.
در آن زمان کاربر متن را با صفحهکلید وارد میکرد، دادهها از طریق سیگنالهای الکتریکی به کامپیوتر مرکزی ارسال میشدند و پس از پردازش، پاسخ دوباره به دستگاه برمیگشت تا روی رولهای کاغذ چاپ شود.
این دستگاهها که با نام Teletype (TTY) شناخته میشدند، سرعت بالایی هم نداشتند؛ معمولاً چیزی حدود ۱۰ تا ۲۰ کاراکتر در ثانیه چاپ میکردند.
چند سال بعد ترمینالهای ویدیویی معرفی شدند. دیگر خبری از رولهای کاغذی نبود و خروجی مستقیماً روی نمایشگرهای CRT نمایش داده میشد. دستگاههایی مانند DEC VT100 یکی از معروفترین نمونههای این نسل بودند و تجربهی کار با کامپیوتر را متحول کردند.
ادامه دارد....
@Amin_Techlab
وقتی در لینوکس ترمینال را باز میکنید و یک دستور مثل
ls یا pwd را اجرا میکنید، واقعاً چه اتفاقی میافتد؟قبل از اینکه سراغ شل برویم، باید کمی به گذشته برگردیم.
دهههای ۱۹۵۰ و ۱۹۶۰ چیزی به اسم «ترمینال» اصلاً یک پنجرهی نرمافزاری نبود؛ بلکه یک دستگاه فیزیکی بود که تنها وظیفهاش دریافت ورودی از کاربر و نمایش خروجی کامپیوتر بود.
در آن زمان کاربر متن را با صفحهکلید وارد میکرد، دادهها از طریق سیگنالهای الکتریکی به کامپیوتر مرکزی ارسال میشدند و پس از پردازش، پاسخ دوباره به دستگاه برمیگشت تا روی رولهای کاغذ چاپ شود.
این دستگاهها که با نام Teletype (TTY) شناخته میشدند، سرعت بالایی هم نداشتند؛ معمولاً چیزی حدود ۱۰ تا ۲۰ کاراکتر در ثانیه چاپ میکردند.
چند سال بعد ترمینالهای ویدیویی معرفی شدند. دیگر خبری از رولهای کاغذی نبود و خروجی مستقیماً روی نمایشگرهای CRT نمایش داده میشد. دستگاههایی مانند DEC VT100 یکی از معروفترین نمونههای این نسل بودند و تجربهی کار با کامپیوتر را متحول کردند.
ادامه دارد....
@Amin_Techlab
❤2🔥2👾2
Amin'sTechLab
پشت صحنهی یک دستور در لینوکس! (۱/۱۰) وقتی در لینوکس ترمینال را باز میکنید و یک دستور مثل ls یا pwd را اجرا میکنید، واقعاً چه اتفاقی میافتد؟ قبل از اینکه سراغ شل برویم، باید کمی به گذشته برگردیم. دهههای ۱۹۵۰ و ۱۹۶۰ چیزی به اسم «ترمینال» اصلاً یک پنجرهی…
پشت صحنهی یک دستور در لینوکس!! (۲/۱۰)
امروزه دیگر آن دستگاههای بزرگ وجود ندارند، اما مفهوم آنها همچنان زنده است.
وقتی برنامهای مثل GNOME Terminal، Konsole یا Windows Terminal را باز میکنید، در واقع با یک Terminal Emulator کار میکنید؛ نرمافزاری که رفتار همان ترمینالهای قدیمی را شبیهسازی میکند.
به همین دلیل هنوز هم اصطلاحاتی مانند TTY و PTS را در لینوکس میبینیم؛ یادگار روزهایی که ترمینال یک سختافزار واقعی بود.
اما یک نکتهی مهم وجود دارد:
ترمینال هیچ دستوری را اجرا نمیکند.
وظیفهی آن فقط دریافت ورودی و نمایش خروجی است.
اجرای دستورات بر عهدهی برنامهای دیگر به نام Shell است.
شل نه سیستمعامل است و نه کرنل؛ فقط یک برنامه است، درست مثل هر برنامهی دیگری.
کلمهی Shell به معنی «پوسته» است؛ لایهای که بین کاربر و کرنل قرار میگیرد و ارتباط میان آنها را برقرار میکند.
مثلاً وقتی دستور زیر را اجرا میکنید:
شل ابتدا بررسی میکند که فایل اجرایی
برای این کار از متغیر محیطی PATH استفاده میکند:
خروجی چیزی شبیه این خواهد بود:
شل این مسیرها را بهترتیب جستجو میکند تا فایل اجرایی موردنظر را پیدا کند.
به همین دلیل اگر یک فایل اجرایی داخل پوشهی فعلی داشته باشید، فقط نوشتن نام آن کافی نیست و باید مسیرش را هم مشخص کنید:
همین طراحی باعث میشود فایلهای ناشناس داخل پوشهی فعلی بهصورت تصادفی اجرا نشوند و امنیت سیستم حفظ شود.
ادامه دارد ....
@Amin_TechLab
امروزه دیگر آن دستگاههای بزرگ وجود ندارند، اما مفهوم آنها همچنان زنده است.
وقتی برنامهای مثل GNOME Terminal، Konsole یا Windows Terminal را باز میکنید، در واقع با یک Terminal Emulator کار میکنید؛ نرمافزاری که رفتار همان ترمینالهای قدیمی را شبیهسازی میکند.
به همین دلیل هنوز هم اصطلاحاتی مانند TTY و PTS را در لینوکس میبینیم؛ یادگار روزهایی که ترمینال یک سختافزار واقعی بود.
اما یک نکتهی مهم وجود دارد:
ترمینال هیچ دستوری را اجرا نمیکند.
وظیفهی آن فقط دریافت ورودی و نمایش خروجی است.
اجرای دستورات بر عهدهی برنامهای دیگر به نام Shell است.
شل نه سیستمعامل است و نه کرنل؛ فقط یک برنامه است، درست مثل هر برنامهی دیگری.
کلمهی Shell به معنی «پوسته» است؛ لایهای که بین کاربر و کرنل قرار میگیرد و ارتباط میان آنها را برقرار میکند.
مثلاً وقتی دستور زیر را اجرا میکنید:
ls
شل ابتدا بررسی میکند که فایل اجرایی
ls کجا قرار دارد.برای این کار از متغیر محیطی PATH استفاده میکند:
echo $PATH
خروجی چیزی شبیه این خواهد بود:
/usr/local/bin:/usr/bin:/bin
شل این مسیرها را بهترتیب جستجو میکند تا فایل اجرایی موردنظر را پیدا کند.
به همین دلیل اگر یک فایل اجرایی داخل پوشهی فعلی داشته باشید، فقط نوشتن نام آن کافی نیست و باید مسیرش را هم مشخص کنید:
./my_app
همین طراحی باعث میشود فایلهای ناشناس داخل پوشهی فعلی بهصورت تصادفی اجرا نشوند و امنیت سیستم حفظ شود.
ادامه دارد ....
@Amin_TechLab
❤4👾2