1162 просмотра
от 4 июня 2024
C/C++

Вопросы и ответы с собеседований по C

Вопросы и ответы с собеседований C-разработчиков. Указатели, функция main(), особенности работы с gets(), strcpy(), free(), разница между sprintf() и printf(), утечки памяти, статические сущности и многое другое.

1

Какого типа значение возвращает main()?

Скомпилируется ли следующий код? Если да, то какие проблемы могут возникнуть? #include <stdio.h> 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 позволяет программам возвращать код статуса, что очень важно, когда программа выполняется как часть скрипта, и внутри скрипта есть условия, которые зависят от результата выполнения программы.

Комментарии
0/3000
2

Что такое указатель?

Указатель — это переменная, которая хранит адрес памяти объекта. Указатели широко используются в C и C++, например, в подобных случаях: 1. Для выделения новых объектов в куче, 2. Для передачи функций другим функциям 3. Для итерации элементов в массивах или других структурах данных.

Комментарии
0/3000
3

Различия sprintf() и printf()?

Метод sprint() работает аналогично методу printf() за исключением одной небольшой детали. Метод printf() записывает вывод на экран консоли, тогда как метод sprintf() записывает вывод в массив символов.

Комментарии
0/3000
4

Расскажите про порядок аргументов printf()

#include <stdio.h> 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, например, такой проблемы нет.

Комментарии
0/3000
5

Расскажите про функцию gets()

В приведенной программе есть проблема. Можете её найти? #include <stdio.h> 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.

Комментарии
0/3000
6

Расскажите про функцию strcpy()

Приведенный код реализует простейшую защиту по паролю. Можно ли вы взломать эту защиту, не зная пароля? #include <stdio.h> 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().

Комментарии
0/3000
7

Расскажите про функцию free()

Следующая программа вылетает с ошибкой сегментации, если ввести freeze. Однако, если ввести zebra, то все будет хорошо. Почему? #include <stdio.h> 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() и ошибке сегментации.

Комментарии
0/3000
8

Расскажите про функции atexit() и _exit()

#include <stdio.h> void func(void) { printf("\n Cleanup function called \n"); return; } int main(void) { int i = 0; atexit(func); for(; i<0xffffff; i++); _exit(0); } Вопрос: В приведённом коде функция func() не вызывается. Почему? Ответ: Из-за функции _exit(). Эта функция не вызывает функции очистки вроде atexit(). Если нужно, чтобы функции atexit() все-таки вызывались, нужно воспользоваться exit() или ключевым словом return.

Комментарии
0/3000
9

Что не так со следующей функцией?

int square(volatile int *ptr) { return *ptr * *ptr; } Эта функция потенциально опасна. Назначение кода состоит в возвращении квадрата значения, указанного при помощи ptr. Однако, поскольку *ptr указывает на volatile переменную, компилятор сгенерирует код, который выглядит примерно так: int square(volatile int *ptr) { int a,b; a = *ptr; b = *ptr; return a * b; } Поскольку значение переменной, на которую указывает *ptr, может неожиданно измениться, то существует возможность, что а и b будут разными. Следовательно, этот код может возвратить число, которое не будет квадратом! Правильный вариант кода в данном случае такой: int square(volatile int *ptr) { int a; a = *ptr; return a * a; }

Комментарии
0/3000
10

Течёт ли тут память?

#include <stdio.h> void main(void) { char *ptr = (char*)malloc(10); if(NULL == ptr) { printf("\n Malloc failed \n"); return; } else { // Do some processing } return; } Да, этот код не освобождает память после ее выделения, но это не приведет к утечке памяти, ведь программа завершается почти сразу же. Операционная система автоматически очищает всю задействованную программой память при ее завершении. Однако, если вышеприведенный код будет находиться в цикле вроде while, это приведет к серьезным утечкам памяти.

Комментарии
0/3000
11

void* и структуры C

Вопрос: Напишите функцию, которая принимает аргумент любого типа, а возвращает int. И еще, можно ли будет передать ей более чем один аргумент? Ответ: Функция, которая принимает аргумент любого типа, выглядит так: int func(void *ptr) Если нужно передать более одного аргумента, тогда этой функции нужно передавать в качестве аргумента указатель на структуру, а в полях структуры - аргументы, которые нужно передать.

Комментарии
0/3000
12

Операторы * и ++

Вопрос: Что напечатает данная программа и почему? #include <stdio.h> int main(void) { char *ptr = "Linux"; printf("\n [%c] \n",*ptr++); printf("\n [%c] \n",*ptr); return 0; } Ответ: Программа выведет следующее: [L] [i] Так как приоритет операторов ++ и * одинаков, то обработка *ptr++ будет осуществляться справа налево. Следуя этой логике, сначала вычисляется *ptr++, а затем *ptr. Таким образом, получаем 'L'. Так как к ptr был применен постфиксный оператор ++, второй printf()напечатает 'i'.

Комментарии
0/3000
13

Реализуйте процесс, изменяющий свое имя

Напишите программу, которая изменяет свое имя во время выполнения. #include <stdio.h> int main(int argc, char *argv[]) { int i = 0; char buff[100]; memset(buff,0,sizeof(buff)); strncpy(buff, argv[0], sizeof(buff)); memset(argv[0],0,strlen(buff)); strncpy(argv[0], "NewName", 7); // Simulate a wait. Check the process // name at this point. for(; i<0xffffffff; i++); return 0; } или #include <stdio.h> int main(int argc, char *argv[]) { argv[0][0] = 65; sleep(10); }

Комментарии
0/3000
14

Адрес локальной переменной

#include <stdio.h> int* inc(int val) { int a = val; a++; return &a; } int main(void) { int a = 10; int *val = inc(a); printf("\n Incremented value is equal to [%d] \n", *val); return 0; } Вопрос: Есть ли проблемы с данным кодом? Если да, то как от них можно избавиться? Ответ: Хотя эта программа может отрабатывать нормально, однако у нее есть серьезная ошибка в функции inc(). Эта функция возвращает адрес локальной переменной. Время жизни этой локальной переменной - это время, пока выполняется функция inc(). После того, как функция inc() отработала, использование адреса локальной переменной может приводить к непредсказуемым результатам. Эту программу можно исправить, если передавать в функцию inc() не значение переменной a, а ее адрес. Таким образом, мы будем вносить изменения только по этому адресу, и не будем задействовать локальные адреса переменных.

Комментарии
0/3000
15

В каких случаях используется ключевое слово static?

Спецификатор static в языке Си используется в трёх случаях: 1. Переменная, описанная внутри тела функции как статическая, сохраняет свое значение между вызовами функции. 2. Переменная, описанная как статическая внутри файла, но снаружи тела функции, доступна для всех функций в пределах этого файла и не доступна функциям любых других файлов. То есть это локализованная глобальная переменная. 3. Функции, описанные внутри файла как статические, могут быть вызваны только другими функциями из этого файла. То есть область видимости функции локализована файлом, внутри которого она описана.

Комментарии
0/3000
16

Зачем используется volatile?

Ключевое слово volatile информирует компилятор о том, что переменная может быть изменена не только из текущего выполняемого кода, но и из других мест. Тогда компилятор будет избегать определенных оптимизаций этой переменной. Примеры volatile переменных: 1. Регистры в периферийных устройствах (например, регистры состояния) 2. Глобальные переменные, используемые в обработчиках прерываний. 3. Глобальные переменные, используемые совместно несколькими задачами в многопотоковом приложении.

Комментарии
0/3000
17

Может ли указатель быть volatile?

Да. Хотя это не общепринятый случай. Например, когда обработчик прерываний изменяет указатель на буфер.

Комментарии
0/3000
Вопросники по C/C++
C/C++
26 вопросов
Вопросы и ответы с собеседований по C++
1408 просмотров
C/C++
17 вопросов
Вопросы и ответы с собеседований по C
1162 просмотра
Хотите заработать на создании вопросников?
Стать редактором
Смежные категории
Computer Science
11 вопросов
Вопросы и ответы про интернет-протоколы
1464 просмотра
Computer Science
28 вопросов
Объяснение паттернов проектирования с примерами
1423 просмотра
Базы данных
10 вопросов
Вопросы с собеседований о репликации баз данных
1457 просмотров
Базы данных
60 вопросов
Вопросы и ответы с собеседований по SQL
2377 просмотров
Computer Science
15 вопросов
Вопросы и ответы с собеседований по DDD
1526 просмотров
Docker
7 вопросов
Коллекция полезных команд для Docker
1321 просмотр
Рекомендуем
Computer Science
28 вопросов
Объяснение паттернов проектирования с примерами
1423 просмотра
Computer Science
11 вопросов
Вопросы и ответы про интернет-протоколы
1464 просмотра
Базы данных
60 вопросов
Вопросы и ответы с собеседований по SQL
2377 просмотров
Computer Science
13 вопросов
Вопросы и ответы с собеседований про ООП
1230 просмотров
Git
20 вопросов
Вопросы и ответы с собеседований по Git
1783 просмотра
Computer Science
12 вопросов
Вопросы с собеседований про операционные системы
1061 просмотр
Другие разделы

Лента

Активность пользователей Девстанции

Перейти к ленте

Лидеры

Рейтинг самых результативных пользователей сообщества

Перейти к лидерам

Треды

Общение по интересам и связь с разработчиками

Перейти к тредам

Задачи

Решение алгоритмических задач с собеседований

Перейти к задачам

Вопросы

Ответы на вопросы с технических собеседований

Вы находитесь здесь

Викторины

Интерактивные викторины по вопросам с собеседований

Перейти к викторинам
Мы в Telegram
Новости проекта, общение с разработчиками, общение по интересам - присоединяйтесь!