ترجمة آلية تحت المراجعة
استرداد
المصدر الأصليقائمة تنفيذ التمرين
تُحفظ علامات هذه القائمة على هذا الجهاز والمتصفح فقط. أنشئ حسابًا مفعّلًا لحفظ إنجاز التمرين في مسارك.
استرداد

مشكلة بحاجة إلى حل
تحسبًا لهذه المشكلة، أمضينا الأيام العديدة الماضية في التقاط صور حول الحرم الجامعي، وتم حفظها جميعًا على كاميرا رقمية بتنسيق JPEG على بطاقة ذاكرة. لسوء الحظ، قمنا بحذفهم جميعًا بطريقة ما! لحسن الحظ، في عالم الكمبيوتر، كلمة "محذوف" لا تعني "محذوف" بقدر ما تعني "منسية". على الرغم من إصرار الكاميرا على أن البطاقة أصبحت الآن فارغة، إلا أننا على يقين من أن هذا ليس صحيحًا تمامًا. في الواقع، نأمل (أتوقع!) أن تتمكن من كتابة برنامج يستعيد الصور لنا!
في ملف يسمى recover.c في مجلد يسمى recover، اكتب برنامجًا لاستعادة ملفات JPEG من بطاقة الذاكرة.
رمز التوزيع
بالنسبة لهذه المشكلة، ستقوم بتوسيع وظيفة الشيفرة المقدمة لك من قبل موظفي CS50.
قم بتنزيل كود التوزيع
قم بتسجيل الدخول cs50.dev، انقر على نافذتك الطرفية، وقم بالتنفيذ cd بمفرده. يجب أن تجد أن مطالبة النافذة الطرفية الخاصة بك تشبه ما يلي:
$
التنفيذ التالي
wget https://cdn.cs50.net/2026/x/psets/4/recover.zip
من أجل تنزيل ملف ZIP يسمى recover.zip في مساحة الشيفرة الخاصة بك.
ثم نفذ
unzip recover.zip
لإنشاء مجلد يسمى recover. لم تعد بحاجة إلى ملف ZIP، حتى تتمكن من التنفيذ
rm recover.zip
ثم قم بالرد بـ "y" متبوعًا بـ Enter عند المطالبة لإزالة ملف ZIP الذي قمت بتنزيله.
الآن اكتب
cd recover
متبوعًا بـ Enter للانتقال إلى (i.e.، open) هذا الدليل. يجب أن تشبه مطالبتك الآن ما يلي.
recover/ $
تنفيذ ls بمفرده، ومن المفترض أن تشاهد ملفين: recover.c و card.raw.
الخلفية
على الرغم من أن ملفات JPEG أكثر تعقيدًا من ملفات BMP، إلا أن ملفات JPEG تحتوي على "توقيعات"، وهي أنماط من وحدات البايت التي يمكنها تمييزها عن تنسيقات الملفات الأخرى. على وجه التحديد، البايتات الثلاثة الأولى من ملفات JPEG هي
0xff 0xd8 0xff
من البايت الأول إلى البايت الثالث، من اليسار إلى اليمين. أما البايت الرابع فهو إما 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee، أو 0xef. وبعبارة أخرى، فإن البتات الأربعة الأولى من البايت الرابع هي 1110.
من المحتمل أنك إذا وجدت هذا النمط المكون من أربع بايتات على الوسائط المعروفة بتخزين الصور (e.g.، بطاقة الذاكرة الخاصة بي)، فإنها تحدد بداية ملف JPEG. لكي نكون منصفين، قد تواجه هذه الأنماط على بعض الأقراص عن طريق الصدفة البحتة، لذا فإن استعادة البيانات ليست علمًا دقيقًا.
لحسن الحظ، تميل الكاميرات الرقمية إلى تخزين الصور الفوتوغرافية بشكل متجاور على بطاقات الذاكرة، حيث يتم تخزين كل صورة مباشرة بعد الصورة التي تم التقاطها مسبقًا. وبناء على ذلك، فإن بداية ملف JPEG عادة ما تشير إلى نهاية ملف آخر. ومع ذلك، غالبًا ما تقوم الكاميرات الرقمية بتهيئة البطاقات باستخدام نظام ملفات FAT الذي يبلغ "حجم الكتلة" فيه 512 بايت (B). المعنى الضمني هو أن هذه الكاميرات تكتب فقط على تلك البطاقات بوحدات 512 B. وبالتالي فإن الصورة التي يبلغ حجمها 1 ميجابايت (i.e.، 1,048,576 B) تشغل 1048576 ÷ 512 = 2048 "كتلة" على بطاقة الذاكرة. ولكن الأمر نفسه ينطبق أيضًا على الصورة التي يكون حجمها أصغر بمقدار بايت واحد (i.e., 1,048,575 B)! المساحة المهدرة على القرص تسمى "المساحة الراكدة". غالبًا ما ينظر محققو الطب الشرعي إلى المساحة الفارغة بحثًا عن بقايا البيانات المشبوهة.
المغزى من كل هذه التفاصيل هو أنك، أيها المحقق، ربما تستطيع كتابة برنامج يتكرر على نسخة من بطاقة الذاكرة الخاصة بي، ويبحث عن توقيعات ملفات JPEG. في كل مرة تجد فيها توقيعًا، يمكنك فتح ملف جديد للكتابة والبدء في ملء هذا الملف بالبايتات من بطاقة الذاكرة الخاصة بي، ولا يتم إغلاق هذا الملف إلا عندما تواجه توقيعًا آخر. علاوة على ذلك، بدلاً من قراءة بايتات بطاقة الذاكرة الخاصة بي واحدة تلو الأخرى، يمكنك قراءة 512 بايت منها في المرة الواحدة في المخزن المؤقت من أجل الكفاءة. بفضل FAT، يمكنك أن تثق في أن توقيعات ملفات JPEG ستكون "محاذاة للكتل". وهذا يعني أنك تحتاج فقط إلى البحث عن تلك التوقيعات في البايتات الأربع الأولى للكتلة.
عليك أن تدرك، بالطبع، أن ملفات JPEG يمكن أن تمتد إلى كتل متجاورة. وبخلاف ذلك، لا يمكن أن يكون حجم أي ملف JPEG أكبر من 512 بايت. لكن البايت الأخير من ملف JPEG قد لا يقع في نهاية الكتلة. أذكر إمكانية وجود مساحة الركود. ولكن لا داعي للقلق. نظرًا لأن بطاقة الذاكرة هذه كانت جديدة تمامًا عندما بدأت في التقاط الصور، فمن المحتمل أن تكون الشركة المصنعة قد "صفرت" (i.e.، مليئة بالصفر)، وفي هذه الحالة سيتم ملء أي مساحة فارغة بالصفر. لا بأس إذا انتهت هذه الأصفار في ملفات JPEG التي تستردها؛ ينبغي أن تظل قابلة للعرض.
الآن، ليس لدي سوى بطاقة ذاكرة واحدة، ولكن هناك الكثير منكم! وهكذا مضيت قدمًا وأنشأت "صورة جنائية" للبطاقة، حيث قمت بتخزين محتوياتها، بايت بعد بايت، في ملف يسمى card.raw. حتى لا تضيع وقتك في تكرار ملايين الأصفار دون داعٍ، فقد قمت فقط بتصوير الميجابايت القليلة الأولى من بطاقة الذاكرة. ولكن يجب أن تجد في النهاية أن الصورة تحتوي على 50 ملفًا بتنسيق JPEG.
المواصفات
تنفيذ برنامج يسمى recover الذي يستعيد ملفات JPEG من صورة الطب الشرعي.
- قم بتنفيذ برنامجك في ملف يسمى
recover.cفي دليل يسمىrecover. - يجب أن يقبل برنامجك وسيطة سطر أوامر واحدة بالضبط، وهي اسم الصورة الجنائية التي يمكن استرداد ملفات JPEG منها.
- إذا لم يتم تنفيذ برنامجك باستخدام وسيطة سطر أوامر واحدة بالضبط، فيجب أن يذكر المستخدم بالاستخدام الصحيح، و
main1. - إذا تعذر فتح صورة الطب الشرعي للقراءة، فيجب على برنامجك إبلاغ المستخدم بذلك، و
main1. - يجب تسمية كل الملفات التي تقوم بإنشائها
###.jpg، حيث###هو رقم عشري مكون من ثلاثة أرقام، يبدأ بـ000للصورة الأولى والعد التصاعدي. - برنامجك إذا كان يستخدم
malloc، يجب ألا يتسرب أي ذاكرة.
تلميحات
اكتب بعض الشيفرة الزائفة قبل كتابة المزيد من الشيفرة
إذا لم تكن متأكدًا من كيفية حل المشكلة الأكبر، فقم بتقسيمها إلى مشكلات أصغر ربما يمكنك حلها أولاً. على سبيل المثال، هذه المشكلة ليست سوى عدد قليل من المشاكل:
- قبول وسيطة سطر أوامر واحدة: اسم بطاقة الذاكرة
- افتح بطاقة الذاكرة
- بينما لا تزال هناك بيانات متبقية للقراءة في بطاقة الذاكرة
- قم بإنشاء ملفات JPEG من البيانات
دعنا نكتب بعض الأكواد الزائفة كتعليقات لتذكيرك بفعل ذلك:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
// Accept a single command-line argument
// Open the memory card
// While there's still data left to read from the memory card
// Create JPEGs from the data
}
تحويل الشيفرة الكاذب إلى كود
أولاً، فكر في كيفية قبول وسيطة سطر أوامر واحدة. إذا أساء المستخدم استخدام البرنامج، فيجب عليك إخباره بالاستخدام الصحيح للبرنامج.
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
// Accept a single command-line argument
if (argc != 2)
{
printf("Usage: ./recover FILE\n");
return 1;
}
// Open the memory card
// While there's still data left to read from the memory card
// Create JPEGs from the data
}
الآن بعد أن قمت بالتحقق من الاستخدام الصحيح، يمكنك فتح بطاقة الذاكرة. ضع في اعتبارك أنه يمكنك فتح card.raw برمجياً باستخدام fopen، كما هو موضح أدناه.
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
// Accept a single command-line argument
if (argc != 2)
{
printf("Usage: ./recover FILE\n");
return 1;
}
// Open the memory card
FILE *card = fopen(argv[1], "r");
// While there's still data left to read from the memory card
// Create JPEGs from the data
}
يجب عليك بالطبع التحقق للتأكد من فتح الملف بشكل صحيح! إذا لم يكن الأمر كذلك، فأخبر المستخدم واخرج من البرنامج: سنترك هذا الجزء لك.
بعد ذلك، يجب أن يقرأ برنامجك البيانات من البطاقة التي فتحتها، حتى لا يكون هناك أي بيانات لقراءتها. على طول الطريق، يجب أن يستعيد برنامجك كل ملفات JPEG منها card.raw، وتخزين كل منها كملف منفصل في دليل العمل الحالي الخاص بك.
فكر أولاً في كيفية القراءة card.raw على طول الطريق. تذكر أنه لقراءة البيانات من ملف ما، فإنك تحتاج إلى تخزين تلك البيانات مؤقتًا في "مخزن مؤقت". وأذكر كذلك ذلك card.raw يخزن البيانات في كتل بحجم 512 بايت. على هذا النحو، من المحتمل أنك ستحتاج إلى إنشاء مخزن مؤقت بسعة 512 بايت لتخزين كتل من البيانات أثناء قراءتها بشكل تسلسلي. إحدى الطرق للقيام بذلك هي استخدام uint8_t اكتب من stdint.h، الذي يخزن بالضبط 8 بتات (1 بايت). النوع يسمى uint8_t نظرًا لأنه يخزن عددًا صحيحًا غير موقّع/موجب/غير سالب يتطلب 8 بتات من المساحة (i.e.، بايت واحد).
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
// Accept a single command-line argument
if (argc != 2)
{
printf("Usage: ./recover FILE\n");
return 1;
}
// Open the memory card
FILE *card = fopen(argv[1], "r");
// Create a buffer for a block of data
uint8_t buffer[512];
// While there's still data left to read from the memory card
// Create JPEGs from the data
}
من المحتمل لا أفضل فكرة هي استخدام 512 كـ "الرقم السحري" هنا. من المحتمل أن تتمكن من تحسين هذا التصميم بشكل أكبر!
الآن، فكر في كيفية قراءة البيانات من بطاقة الذاكرة. لكل صفحة الدليل, fread يُرجع عدد البايتات التي قرأها، وفي هذه الحالة يجب أن يُرجع إما 512 أو 0، بالنظر إلى ذلك
يحتوي card.raw على عدد من الكتل بحجم 512 بايت. من أجل قراءة كل كتلة من card.rawبعد فتحه بـ fopen، يجب أن يكفي استخدام حلقة كهذه.
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
// Accept a single command-line argument
if (argc != 2)
{
printf("Usage: ./recover FILE\n");
return 1;
}
// Open the memory card
FILE *card = fopen(argv[1], "r");
// Create a buffer for a block of data
uint8_t buffer[512];
// While there's still data left to read from the memory card
while (fread(buffer, 1, 512, card) == 512)
{
// Create JPEGs from the data
}
}
بهذه الطريقة، في أقرب وقت fread يعود 0 (وهو فعليًا false)، ستنتهي حلقتك.
أخيرًا، الأمر متروك لك لتحديد كيفية إنشاء ملفات JPEG برمجيًا بينما تستمر في القراءة من card.raw. لهذا، قد تجد أدناه الإرشادات التفصيلية مفيد لك.
ضع في اعتبارك أن برنامجك يجب أن يقوم بترقيم الملفات التي يخرجها عن طريق تسمية كل منها ###.jpg، حيث ### هو رقم عشري مكون من ثلاثة أرقام 000 للأعلى. صداقة sprintf ولاحظ ذلك
يقوم sprintf بتخزين سلسلة منسقة في موقع في الذاكرة. نظرا للمقررة ###.jpg لاسم ملف JPEG، ما عدد البايتات التي يجب عليك تخصيصها لهذه السلسلة؟ (لا تنسَ شخصية NUL!)
للتحقق مما إذا كانت ملفات JPEG التي يرسلها برنامجك صحيحة، ما عليك سوى النقر نقرًا مزدوجًا وإلقاء نظرة! إذا ظهرت كل صورة سليمة، فمن المحتمل أن تكون عمليتك ناجحة!
وبالطبع، تذكر أن تفعل ذلك fclose كل ملف فتحته fopen!
حافظ على نظافة دليل العمل الخاص بك
من المحتمل أن تكون ملفات JPEG التي تنشرها المسودة الأولى من الشيفرة الخاص بك غير صحيحة. (إذا قمت بفتحها ولم ترَ أي شيء، فمن المحتمل أنها غير صحيحة!) قم بتنفيذ الأمر أدناه لحذف جميع ملفات JPEG الموجودة في دليل العمل الحالي لديك.
rm *.jpg
إذا كنت تفضل عدم مطالبتك بتأكيد كل عملية حذف، فقم بتنفيذ الأمر أدناه بدلاً من ذلك.
rm -f *.jpg
فقط كن حذرًا بشأن ذلك -f التبديل، لأنه "يفرض" الحذف دون مطالبتك.
الإرشادات التفصيلية
كيفية الاختبار
تشغيل البرنامج
./recover card.raw
صحة
check50 cs50/problems/2026/x/recover
النمط
style50 recover.c
كيفية الإرسال
في جهازك الطرفي، قم بتنفيذ ما يلي لإرسال عملك، والإجابة على المطالبات التي تظهر أيضًا.
submit50 cs50/problems/2026/x/recover