Ответы на вопросы для собеседования по C
Какого типа значение возвращает main()?
Скомпилируется ли следующий код? Если да, то какие проблемы могут возникнуть?
void main(void)
{
char *ptr = (char*)malloc(10);
if(NULL == ptr)
{
printf("\n Malloc failed \n");
return;
}
else
{
// Do some processing
free(ptr);
}
return;
}
Этот код скомпилируется без ошибок, но с варнингом (на большинстве компиляторов) о том, что значение, возвращаемое функцией
main()
, должно иметь тип int
, а не void
.
Тип int
позволяет программам возвращать код статуса, что очень важно, когда программа выполняется как часть скрипта, и внутри скрипта есть условия, которые зависят от результата выполнения программы.
Что такое указатель?
Указатель — это переменная, которая хранит адрес памяти объекта. Указатели широко используются в C и C++, например, в подобных случаях: 1. Для выделения новых объектов в куче, 2. Для передачи функций другим функциям 3. Для итерации элементов в массивах или других структурах данных.
Различия sprintf() и printf()?
Метод sprint()
работает аналогично методу printf()
за исключением одной небольшой детали. Метод printf()
записывает вывод на экран консоли, тогда как метод sprintf()
записывает вывод в массив символов.
Расскажите про порядок аргументов printf()
int main(void)
{
int a = 10, b = 20, c = 30;
printf("\n %d..%d..%d \n", a+b+c, (b = b*2), (c = c*2));
return 0;
}
Вопрос: Что выведет данный код?
Ответ: Программа выведет следующее:
110..40..60
Несмотря на то, что аргументы функции printf
отображаются слева направо, вычисляются они справа налево. Однако, порядок вычисления аргументов стандартом не определен. Аргументы кладутся в стек справа налево, но порядок их вычисления может быть иным.
В Java, например, такой проблемы нет.
Расскажите про функцию gets()
В приведенной программе есть проблема. Можете её найти?
int main(void)
{
char buff[10];
memset(buff,0,sizeof(buff));
gets(buff);
printf("\n The buffer entered is [%s]\n",buff);
return 0;
}
Скрытая проблема в этом коде – это использование функции
gets()
. Эта функция принимает строку со стандартного ввода без проверки размера буфера, в который будет помещена эта строка. Это запросто может привести к переполнению буфера. В данном случае лучше использовать другую стандартную функцию – fgets()
.
Дополнение: gets()
является deprecated
.
Расскажите про функцию strcpy()
Приведенный код реализует простейшую защиту по паролю. Можно ли вы взломать эту защиту, не зная пароля?
int main(int argc, char *argv[])
{
int flag = 0;
char passwd[10];
memset(passwd,0,sizeof(passwd));
strcpy(passwd, argv[1]);
if(0 == strcmp("LinuxGeek", passwd))
{
flag = 1;
}
if(flag)
{
printf("\n Password cracked \n");
}
else
{
printf("\n Incorrect passwd \n");
}
return 0;
}
Логику кода аутентификации, приведенного выше, можно обойти при помощи уязвимости в функции
strcpy()
. Эта функция копирует пароль, предоставленный пользователем, в буфер ‘passwd’
, не проверяя, достаточно ли в этом буфере места.
Предположим, что пользователь введет случайный пароль, имеющий длину, достаточную для того, чтобы заполнить как буфер ‘passwd’
, так и перезаписать область памяти, содержащую изначальное значение 0
переменной flag
. В этом случае, даже если сравнение введенной строки и пароля не пройдет, то все равно проверка флага, который изначально имел нулевое значение, станет ненулевым, и таким образом, защита будет “взломана”.
К примеру:
$ ./psswd aaaaaaaaaaaaa
Password cracked
Здесь можно видеть, что хотя введенный пароль был некорректен, но программа все равно была взломана через ошибку переполнения буфера.
Для защиты от подобных случаев следует пользоваться функцией strncpy()
.
Расскажите про функцию free()
Следующая программа вылетает с ошибкой сегментации, если ввести freeze
. Однако, если ввести zebra
, то все будет хорошо. Почему?
int main(int argc, char *argv[])
{
char *ptr = (char*)malloc(10);
if(NULL == ptr)
{
printf("\n Malloc failed \n");
return -1;
}
else if(argc == 1)
{
printf("\n Usage \n");
}
else
{
memset(ptr, 0, 10);
strncpy(ptr, argv[1], 9);
while(*ptr != 'z')
{
if(*ptr == '\0')
break;
else
ptr++;
}
if(*ptr == 'z')
{
printf("\n String contains 'z'\n");
// Do some more processing
}
free(ptr);
}
return 0;
}
Здесь проблема заключается в том, что код изменяет адрес указателя
ptr
(путем инкремента переменной ptr
) внутри цикла while
. Когда пользователь вводит zebra
, цикл while
завершается без единой итерации, поэтому адрес, переданный функции free()
, будет точно такой же, какой был присвоен функцией malloc()
.
Однако, в случае с freeze
, значение переменной ptr
изменяется внутри цикла while
, что приводит к передаче неправильного адреса в функцию free()
и ошибке сегментации.