язык С, адресная арифметика, указатель и malloc

Коллеги, не могу сообразить где проблема.

Есть задачка:

Принять int n ; - кол-во вещестенных чисел в последовательности, вводятся с консоли. Вывести на экран все числа с двумя десятичными знаками. Пример: ввод - 3 4.123 7.011 5.1 // 3 - количество чисел (<100), остальное - сами числа.

вывод - 4.12 7.01 5.10

Вот такой код получился:

#include <stdio.h>
#include <stdlib.h>

main(){
    int n=0,i=0;
    double *pn;

    scanf("%d",&n);                        // Принимаю кол-во чисел в последовательности
    pn=(double *)malloc(n*sizeof(double)); // Присваиваю указатель на первый элемент блока 
                                           //   из n-ячеек памяти размером double 
    while(i<n)                             // Гоняю цикл n-раз
        scanf("%f",pn+i++);                // После присваивания инкрементирую i для 
                                           //   смещения в следующем цикле, по блоку памяти 
                                           //   выделенным malloc
    while(i>0){
        printf("%.2f\n",*(pn + n - i--));  // Вывод чисел n-раз с 2-мя десятичными знаками
    }
    free(pn);                              // Освобождаю память, хотя, она и так 
                                           // освободится после выхода из программы,
                                           // если я правильно понимаю.
}

Вот результат:

![результат

Если я использую указатель типа int * и ввожу int числа, формат меняю с "%f" на "%d", то как будто работает:

как будто работает

не могу понять - почему же не работает с вещественными числами?

Мне бы хотелось разобраться с указателями и адресной арифметикой, поэтому такое решение выбрал.


Ответы (2 шт):

Автор решения: MBo

Формат не тот (для scanf важно)

 while(i<n)                             // Гоняю цикл n-раз
        scanf("%lf",pn+i++);                // После присваивания инрементируюю i для 
                                           //   смещения в следующем цикле, по блоку памяти 
                                           //   выделенным malloc
    while(i>0){
        printf("%.2lf\n",*(pn + n - i--));  // Вывод чисел n-раз с 2-мя десятичными знаками
    }
→ Ссылка
Автор решения: Stanislav Volodarskiy

Компилирую ваш код:

$ gcc -std=c11 -pedantic -Wall -Wextra -Werror -Wwrite-strings -Wconversion temp.c 
temp.c:7:1: error: return type defaults to ‘int’ [-Werror=implicit-int]
    7 | main(){
      | ^~~~
temp.c: In function ‘main’:
temp.c:12:26: error: conversion to ‘long unsigned int’ from ‘int’ may change the sign of the result [-Werror=sign-conversion]
   12 |     pn=(double *)malloc(n*sizeof(double)); // Присваиваю указатель на первый элемент блока
      |                          ^
temp.c:15:17: error: format ‘%f’ expects argument of type ‘float *’, but argument 2 has type ‘double *’ [-Werror=format=]
   15 |         scanf("%f",pn+i++);                // После присваивания инрементируюю i для
      |                ~^  ~~~~~~
      |                 |    |
      |                 |    double *
      |                 float *
      |                %lf
cc1: all warnings being treated as errors
  1. Нет int перед main. На максимальную скорость не влияет.
  2. В произведении n*sizeof(double) смешана знаковая и беззнаковая арифметика. На максимальную скорость не влияет.
  3. scanf читает float и пишет его в double. Это реальная проблема, типы перепутаны.

Если исправить все три ошибки то будет:

#include <stdio.h>
#include <stdlib.h>

int main(){
    int n=0,i=0;
    double *pn;

    scanf("%d",&n);                        // Принимаю кол-во чисел в последовательности
    pn=(double *)malloc((unsigned)n*sizeof(double)); // Присваиваю указатель на первый элемент блока 
                                           //   из n-ячеек памяти размером double 
    while(i<n)                             // Гоняю цикл n-раз
        scanf("%lf",pn+i++);               // После присваивания инрементируюю i для 
                                           //   смещения в следующем цикле, по блоку памяти 
                                           //   выделенным malloc
    while(i>0){
        printf("%.2f\n",*(pn + n - i--));  // Вывод чисел n-раз с 2-мя десятичными знаками
    }
    free(pn);                              // Освобождаю память, хотя, она и так 
                                           // освободится после выхода из программы,
                                           // если я правильно понимаю.
}
$ gcc -std=c11 -pedantic -Wall -Wextra -Werror -Wwrite-strings -Wconversion temp.c 

$ ./a.out
3
4.123 7.011 5.1
4.12
7.01
5.10

Ехидный вопрос: тяжело, наверно, без компилятора программировать?

Дальше мелочи:

  • в malloc не надо писать sizeof(double). Напишите sizeof(*pn), так вы не дублируете тип в двух местах.
  • результат malloc не надо приводить, void * приводится к double * автоматически.
  • выражения pn+i++ и &pn[i++] равносильны. Но первое обычно означает массив начинающийся с адреса pn+i++, а второе адрес одного элемента. Если вы придерживаетесь этого соглашения, программисту, читающему код, легче понять это указатель на один элемент или на массив.
  • C индексами можно играть как угодно, но лучше перебирать массива канонически, через for(int i = 0; i < n; ++i).
  • n можно сделать беззнаковым, будет меньше приведений.
  • переменные объявляются как можно позже. Индексы цикла объявляются в цикле.

Вот это канонический код, который стандартному программисту на С читать и понимать легче:

#include <stdio.h>
#include <stdlib.h>

int main() {
    unsigned n;
    scanf("%u", &n);                       // Принимаю кол-во чисел в последовательности

    double *pn = malloc(n * sizeof(*pn));  // Присваиваю указатель на первый элемент блока 
                                           //   из n-ячеек памяти размером double 

    for (unsigned i = 0; i < n; ++i) {     // Гоняю цикл n-раз, тут вся работа с i
        scanf("%lf", &pn[i]);
    }

    for (unsigned i = 0; i < n; ++i) {     // Гоняю цикл n-раз, тут вся работа с i
        printf("%.2f\n", pn[i]);           // Вывод чисел n-раз с 2-мя десятичными знаками
    }

    free(pn);                              // Освобождаю память, хотя, она и так 
                                           // освободится после выхода из программы,
                                           // если я правильно понимаю.
}
→ Ссылка