Функция с неопределенным количеством параметров (без использования stdarg.h)
Мне нужно создать функцию, которая считает выражение:
(х1 + х3 + х5 + ...) / (х2 + х4 + х6 + ...)
где х1-хn - параметры функции. Количество параметров чётное и они имеют разный тип, при вызове функции я перед каждым числом указываю его тип. Я написала функцию, используя макросы из <stdarg.h> (см. вопрос Функция с неопределенным количеством параметров).
Как можно выполнить это же задание, не используя макросы из <stdarg.h>, а используя только указатели? Вот приблизительно такое написала:
void SumIntDouble(int, ...);
void main(void)
{
system("chcp 1251");
SumIntDouble(6, 'i', 1, 'd', 2.0, 'i', 3, 'd', 4.0, 'i', 5, 'd', 6.0);
getchar(); getchar();
}
void SumIntDouble(int k, ...)
{
double sum = 0;
double a = 0; // числитель
double b = 0; // знаменатель
int* p = &k + 1;
for (int i = 0; i < k; i++) {
switch (*p) {
case'i': {
p += sizeof(char);
if (i % 2 == 0)
a += *p;
else
b += *p;
p++;
break;
}
case'd': {
p += sizeof(char);
if (i % 2 == 0)
a += *(double*)p;
else
b += *(double*)p;
p += 2; // сдвигаем на double (=2 int)
break;
}
}
}
sum = a / b;
printf(" Сумма: %lf\n", sum);
}
Ответы (4 шт):
анука попробуй вот так
я не запускал так что может не работать )
char *address = (char*)(&k + 1);
char type;
double a, b;
int intData;
double doubleData;
for(int i = 0; i < k; i++)
{
type = *address;
address++;
switch(type)
{
case 'i':
intData = *(int*)(address);
if (i % 2 == 0)
a += (double)intData;
else
b += (double)intData;
address += sizeof(int);
break;
case 'd':
doubleData = *(double*)(address);
if (i % 2 == 0)
a += doubleData;
else
b += doubleData;
address += sizeof(double);
break;
}
}
ЭТО НЕ ОТВЕТ!!
Просто в комментарий не втиснуться. Вот как VC++ 2017 обрабатывает вызов вашей функции в 64-разрядном приложении:
; 9 : SumIntDouble(6, 'i', 1, 'd', 2.0, 'i', 3, 'd', 4.0, 'i', 5, 'd', 6.0);
movsd xmm0, QWORD PTR __real@4018000000000000
mov edx, 105 ; 00000069H
movsd QWORD PTR [rax-24], xmm0
movsd xmm0, QWORD PTR __real@4010000000000000
mov DWORD PTR [rax-32], 100 ; 00000064H
mov DWORD PTR [rax-40], 5
lea r9d, QWORD PTR [rdx-5]
mov DWORD PTR [rax-48], 105 ; 00000069H
lea r8d, QWORD PTR [rdx-104]
movsd QWORD PTR [rax-56], xmm0
lea ecx, QWORD PTR [rdx-99]
movsd xmm0, QWORD PTR __real@4000000000000000
mov DWORD PTR [rax-64], 100 ; 00000064H
mov DWORD PTR [rax-72], 3
mov DWORD PTR [rax-80], 105 ; 00000069H
movsd QWORD PTR [rax-88], xmm0
call ?SumIntDouble@@YAXHZZ ; SumIntDouble
Вот и крутись тут, как знаешь :)
В 32-разрядном попроще:
; 9 : SumIntDouble(6, 'i', 1, 'd', 2.0, 'i', 3, 'd', 4.0, 'i', 5, 'd', 6.0);
movsd xmm0, QWORD PTR __real@4018000000000000
sub esp, 8
movsd QWORD PTR [esp], xmm0
movsd xmm0, QWORD PTR __real@4010000000000000
push 100 ; 00000064H
push 5
push 105 ; 00000069H
sub esp, 8
movsd QWORD PTR [esp], xmm0
movsd xmm0, QWORD PTR __real@4000000000000000
push 100 ; 00000064H
push 3
push 105 ; 00000069H
sub esp, 8
movsd QWORD PTR [esp], xmm0
push 100 ; 00000064H
push 1
push 105 ; 00000069H
push 6
call ?SumIntDouble@@YAXHZZ ; SumIntDouble
add esp, 64 ; 00000040H
Видите? А это один и тот же компилятор...
У вас, кстати, какой?
Update
Для VC++ 2017 32-разрядное приложение (для 64-разрядного работать не будет) вот такое работает:
void SumIntDouble(int, ...);
int main(void)
{
SumIntDouble(6, 'i', 1, 'd', 2.0, 'i', 3, 'd', 4.0, 'i', 5, 'd', 6.0);
getchar(); getchar();
}
void SumIntDouble(int k, ...)
{
double sum = 0;
double a = 0; // числитель
double b = 0; // знаменатель
int* p = &k;
for (int i = 0; i < k; i++)
{
char c = *(++p);
switch (c) {
case'i': {
p++;
if (i % 2 == 0)
a += *p;
else
b += *p;
break;
}
case'd': {
p++;
if (i % 2 == 0)
a += *(double*)p;
else
b += *(double*)p;
p++;
break;
}
}
}
sum = a / b;
printf(" Сумма: %lf\n", sum);
}
если бы не нужно было перемешивать аргументы можно было бы сделать проще.
#include <cstdint>
#include <cstdio>
struct Null {};
Null calc(){ return Null{}; }
template<class F, class S>
struct Sum {
F first;
S second;
auto operator+(Null next) { return *this; }
template<class F1, class S1>
auto operator+(Sum<F1, S1>&& next) {
first += next.first;
second += next.second;
return *this;
}
auto res() { return first/second; }
};
template<class T1, class T2> Sum(const T1&, const T2&) -> Sum<T1, T2>;
template<typename T1,
typename T2,
typename... Other>
auto calc(T1 x1, T2 x2, Other... args) {
return (Sum{x1, x2} + calc(args...));
}
int main(){
auto sum = calc(12.2, 2.3, 3.0, 4.4, (char)5, 6);
printf("%f", sum.res());
return 0;
}
Плюсы:
- Можно складывать любые типы, достаточно описать оператор
+для него - Если аргументов нечётное количество ошибка будет известна на compiletime
- Если аргументы известны в compiletime то и всё вычисление будет в compiletime [1].
Минусы:
- Тип у sum.first и sum.second зависит от первых двух параметров, т.е если первыми будут int то и все вычисление будет усечено до int.
[1] Этот пример компилируется буквально в пару строчек:
calc():
xor eax, eax
ret
.LC1:
.string "%f"
main:
sub rsp, 8
mov edi, OFFSET FLAT:.LC1
mov eax, 1
movsd xmm0, QWORD PTR .LC0[rip]
call printf
xor eax, eax
add rsp, 8
ret
.LC0:
.long 3415682653
.long 1073312485
Как я уже сказал выше в комментариях под вопросом, написать функцию с переменным количеством аргументов без использования средств, предоставляемых заголовочным файлом <stdarg.h> переносимым способом невозможно. Имейте это в виду, и, если нужно, покажите этот ответ своему преподователю.
А теперь смотрите, почему это не возможно с теоретической и почти всегда невозможно c практической сторон.
Теоретическая сторона
С теоретической стороны это невозможно, т. к. этого не позволяет стандарт языка C. В стандарте сказано, что размещение параметров при вызове функции неспецифицированно:
9 Each parameter has automatic storage duration; its identifier is an lvalue.166) The layout of the storage for parameters is unspecified.
— ISO/IEC 9899:2017 N2176 §6.9.1
Это значит, что способ передачи аргументов в функцию полностью зависит от реализации и делать какие-либо предположения о том, как параметры передаются в функцию — бессмысленно.
Практическая сторона
Теперь рассмотрим практическую сторону. Как я уже сказал, способ передачи аргументов в функцию зависит от реализации. Проблема в том, что реализаций может быть много. Очень много. Под реализацией понимается, как минимум:
- компилятор
- hosted или freestanding среда исполнения1;
- целевая архитектура (ISA);
- ABI, в частности — соглашения о вызове (calling conventions);
- …
Количество всех возможных комбинаций очень велико. Причем для каждой такой комбинации код, в общем случае, будет разным. Придется учитывать особенности каждого ABI, и не факт, что ABI позволит получить значения аргументов таким «грязным» способом. Вынужден признать, что в некоторых обстоятельствах мы можем решить задачу, хотя говорить о корректности решения в таких случаях не приходится.
Конкретные примеры
Допустим, что мы заранее знаем, каким компилятором, под какую архитектуру и с каким ABI будет компилироваться программа. Например, это будет GCC, Intel 642 и System V ABI (x86_64 psABI).
Тогда мы сталкиваемся с тем, что в такой конфигурации в принципе не можем решить поставленную задачу. Почему? Потому что, согласно System V ABI, вещественные значения передаются в функцию не через стек, а через специальные регистры %xmm0−%xmm7. А, как вы знаете, язык C не позволяет обращаться к конкретным регистрам процессора.
Тогда, снова сделаем допущение, что нам повезло немного больше и мы компилируем программу под IA-323, i386 System V ABI, i386 GNU/Linux. Согласно такому ABI, параметры передаются через стек и нам настолько повезло, что мы даже знаем их конкретное размещение (см. раздел Function Calling Sequence)!
Напишем код, который вызывал бы нужную нам функцию:
#include <stdio.h>
double sum(unsigned int, ...);
int
main(void)
{
printf("%f\n", sum(3, 1.0, 2.0, 16.0));
}
И проверим нужные смещения отладчиком:
>>> print *((unsigned int *) ($esp + 0))
$1 = 3
>>> print *((double *) ($esp + 4))
$2 = 1
>>> print *((double *) ($esp + 12))
$3 = 2
>>> print *((double *) ($esp + 20))
$4 = 16
Отлично! Теперь можем написать реализацию функцию sum:
double
sum(unsigned int n, ...)
{
double s = 0.0;
for (unsigned int i = 0; i < n; ++i) {
double *p = ((unsigned char *) &n) + sizeof(unsigned int) + sizeof(double) * i;
s += *p;
}
return s;
}
1) За подробностями обращайтесь к разделу «5.1.2 Execution Environement» стандарта C17.
2) Эту ISA часто называют x64, x86_64 или x86-64. Скорее всего, процессор вашего компьютера реализует именно эту архитектуру.
3) Эту ISA также называют x32, i386 или Intel-386.