В отличие от языка C, C++ не предусматривает автоматического преобразования в тех случаях, когда фактические параметры не совпадают по типам с соответствующими им формальными параметрами.
Говорят, что язык C++ обеспечивает строгий контроль типов.
В связи с этой особенностью языка C++, проверка соответствия типов формальных и фактических аргументов выполняется на этапе компиляции.
Строгое согласование по типам между формальными и фактическими аргументами требует, чтобы в модуле до первого обращения к функции было помещено либо ее определение, либо ее описание (прототип), содержащее сведения о типе результата (возвращаемого значения) и о типах всех аргументов. Наличие такого прототипа либо полного определения позволяет компилятору выполнять контроль соответствия типов аргументов.
Прототип (описание) функции может внешне почти полностью совпадать с заголовком ее определения:
тип имя (СпецификацияФормальныхАгрументов);
Основное различие – точка с запятой в конце описания (прототипа). Второе отличие – необязательность имен формальных аргументов в прототипе даже тогда, когда они есть в заголовке определения функции.
Спецификация формальных аргументов может быть пустой (void), а может представлять собой список спецификаций отдельных аргументов, разделенных запятыми. Спецификация каждого аргумента в определении функции имеет вид:
тип имя
Значения по умолчанию
Для аргумента может быть задано (а может отсутствовать) значение по умолчанию. Это значение используется в том случае, если при обращении к функции соответствующий аргумент не передан. В этом случае спецификация аргумента имеет вид
тип имя=ЗначениеПоУмолчанию
Если аргумент имеет значение по умолчанию, то все аргументы, специфицированные после него, также должны иметь значения по умолчанию.
Пример Вычислить n в степени k, где чаще всего k=2. Рекурсивная функция со значением k=2 по умолчанию:
2
3
4
5
{
if (k == 2) return(n*n);
else return(pow(n, k - 1)*n);
}
Вызывать эту функции можно двумя способами:
q = pow(i, 5); // q = i*i*i*i*i;
Значение по умолчанию может быть задано либо при объявлении функции, либо при определении функции, но только один раз.
Перегрузка функций
При перегрузке функция с одним именем по-разному выполняется и возвращает разные значения при обращении к ней с разными по типам и количеству фактическими аргументами.
Например, функция, возвращающая максимальное значение элементов одномерного массива, передаваемого ей в качестве аргумента. Массивы, обрабатываемые в функции, могут содержать элементы разных типов, но пользователь не должен беспокоиться о типе результата. Функция всегда должна возвращать значение того же типа, что и тип массива, переданного в качестве фактического аргумента. В этом случае используются несколько разных варианта функции с одним и тем же именем.
float max(float *a, int n);
double max(double *a, int n);
Для обеспечения перегрузки функций необходимо для каждого имени определить, сколько разных функций связано с ним, т.е. сколько вариантов сигнатур допустимы при обращении к ним.
Распознавание перегруженных функций при вызове выполняется по их сигнатурам. Перегруженные функции поэтому должны иметь одинаковые имена, но спецификации их аргументов должны различаться по количеству и (или) по типам, и (или) по расположению.
При использовании перегруженных функций нужно с осторожностью задавать начальные значения их аргументов. Например, если в одной программе перегружены функции
int sum(int a) { return(a+a); }
то вызов
выдаст ошибку из-за неоднозначности толкования sum().
Встраиваемые функции
В базовом языке Си директива препроцессора #define позволяет использовать макроопределения для записи вызова небольших часто используемых конструкций. Некорректная запись макроопределения может приводить к ошибкам, которые очень трудно найти. Макроопределения не позволяют определять локальные переменные и не выполняют проверки и преобразования аргументов.
Если вместо макроопределения использовать функцию, то это удлиняет объектный код и увеличивает время выполнения программы.
Кроме того, при работе с макроопределениями необходимо тщательно проверять раскрытия макросов, например
rez = SUMMA(x, y)*10;
После работы препроцессора получим:
В С++ для определения функции, которая должна встраиваться как макроопределение используется ключевое слово inline. Вызов такой функции приводит к встраиванию кода inline-функции в вызывающую программу. Определение такой функции может выглядеть следующим образом:
2
3
4
{
return(a + b);
}
При вызове этой функции
будет получен следующий результат:
При определении и использовании встраиваемых функций необходимо придерживаться следующих правил:
- Определение и объявление функций должны быть совмещены и располагаться перед первым вызовом встраиваемой функции.
- Имеет смысл определять inline только очень небольшие функции, поскольку любая inline-функция увеличивает программный код.
- Различные компиляторы накладывают ограничения на сложность встраиваемых функций. Компилятор сам решает, может ли функция быть встраиваемой. Если функция не может быть встраиваемой, компилятор рассматривает ее как обычную функцию.
Таким образом, использование ключевого слова inline для встраиваемых функций и ключевого слова const для определения констант позволяют практически исключить директиву препроцессора #define из употребления.
Назад: Язык C++
Но вот под Windows столкнулся с такой проблемой:
2
3
4
5
6
void my_func(uint8_t a, ... )
{
for(uint8_t i=1; i<100; i++)
x[i] = *( ( &a ) + i );
}
2
3
4
5
6
7
167 137 193 0 44 254 40 0
84 206 40 19 88 206 40 0
50 27 64 0 0 0 0
0 0 0 20 0 0 0
0 0 0 40 0 0 0
...
а вот если сделать функцию таким образом:
2
3
4
5
6
void my_func(uint8_t b, uint8_t a, ... )
{
for(uint8_t i=1; i<100; i++)
x[i] = *( ( &a ) + i );
}
2
3
4
5
6
7
8
44 254 40 0 167 137 185 0
44 254 40 0 84 206 40 23
88 206 40 0 56 27 64 0
0 0 0 10 0 0 0 0
20 0 0 0 30 0 0 0
0 0 0 0 0 0 0
...
Вот что это за чудеса?
QtCreator+MinGW32
тип аргументов функции uint8_t (unsigned char) 1 байт без знака. Данную функцию я делал только чтобы разобраться каким образом мне получить значения аргументов внутри функции. Раньше когда-то я делал такое, там использовались указатели таким образом:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void my_func(uint8_t a, ...) {
uint8_t * pp = &a;
pp += step_stack;
uint8_t p1;
p1 = *pp;
pp += step_stack;
uint8_t p2;
p2 = *pp;
pp += step_stack;
uint8_t p3;
p3 = *pp;
pp += step_stack;
uint8_t p4;
p4 = *pp;
...
}
В данный момент мне нужно сделать библиотеку с несколькими подобными функциями, и вот эту портянку хотел бы заменить макросом чтобы более лаконичный текст был. Хотел сделать указатель на структуру, присвоить ему адрес переменной (&a) и далее в теле функции обращаться к значениям параметров как к полям структуры - когда текст скомпилируется в ассемблерный, то там не должно быть никаких дополнительных команд, только смещение адреса от вершины стека. Но компилятор MinGW создает какой-то непонятный кадр в стеке при вызове такой функции.
Сделать функцию с максимальным количеством аргументов и вызывать, заполняя ненужные нулями - тоже не лучший выход. Дело в том что в одной программе требуется максимум 2 дополнительных аргумента, в другой 6. Операция вызова функции помещает в стек 2 байта или +4 ненужных нуля.
Если передавать как составные байты 32-битного параметра - это будет лапша-код при вызове функции и дополнительные операции на упаковку/распаковку.
Написать 12 функций my_func2(a,p1,p2); my_func3(a,p1,p2,p3); my_func4(a,p1,p2,p3,p4) и т.д. как-то корявенько это.
Важно чтобы при написании и отладке более высокоуровневых модулей программы не отвлекаться на это. Вызов функции в тексте программы должен быть максимально лаконичным, удобочитаемым, интуитивно-понятным.
Я могу конечно расписать задачу более подробно - почему и так не так, и эдак не пойдёт. Думаю это никому не интересно, так как у каждого свои спектры приоритетов.
Вопрос мой в том - как максимально эффективно ловить аргументы в функции с переменным количеством аргументов? Пока вижу только step_stack в сборке проекта прописывать каждому компилятору опцию -Dstep_stack=4 (как предопределённый define). А в теле функции макрос типа
2
3
4
5
6
#define BASE_FUNC_PEREM_ARGS(VALUE) uint8_t p1= (*(&VALUE + step_stack));\
uint8_t p2= (*(&VALUE + step_stack*2));\
uint8_t p3= (*(&VALUE + step_stack*3));\
uint8_t p4= (*(&VALUE + step_stack*4));\
2
3
#define p(VALUE,IND) (*(&VALUE + step_stack*IND))
C MinGW32 мне наверно надо самостоятельно разобраться почему так криво стек наполняется. На MinGW64 работало штатно через 8 байт без всякого "мусора".
2
3
4
int sum(int a) { return(a+a); }
то вызов
int r = sum(2); // ошибка