Почему плохо использовать короткие

Очень часто даже в сценариях, где разработчик гарантирует, что переменная никогда не будет превышать одного байта, а иногда и двух байтов; Многие люди решают использовать типы int для каждой возможной переменной, используемой для представления чисел в диапазоне от 0 до 1.

Почему вместо этого использовать char или short так больно?

Думаю, я слышал, как кто-то сказал, что int - это "более стандартный" тип .. типа. Что это значит. Мой вопрос: имеет ли тип данных int какие-либо определенные преимущества перед short (или другими меньшими типами данных), из-за каких преимуществ люди почти всегда прибегали к int?

Использование short имеет смысл, когда возможен большой массив объектов, иначе есть небольшое преимущество перед int.

chux - Reinstate Monica 28.05.2018 05:18

@chux Верно, а почему именно int? Я бы выбрал short, потому что он имеет небольшое преимущество в пространстве, чем int, который выбирают люди, потому что он выглядит лучше, как кажется? Требуется набрать на 2 клавиши меньше. (Обновлено: не забывать, что целые числа могут иметь разный размер)

Edenia 28.05.2018 05:19
int обычно представляет собой естественный целочисленный тип данных ЦП, и поэтому операции с участием int имеют тенденцию быть более оптимизированными ЦП и компилятором.
Remy Lebeau 28.05.2018 05:22

Собственный размер int обычно выбирается таким образом, чтобы он наилучшим образом соответствовал регистрам процессора. short не занимает полный регистр, поэтому он менее эффективен. Используйте размер, соответствующий тому, что вы делаете. Если у вас есть конкретное требование использовать что-либо, кроме целого числа, сделайте это. В противном случае доверьте свою работу компилятору.

Ken White 28.05.2018 05:22

Итак, int имеет преимущество в производительности по сравнению с short, как я полагаю, на большинстве процессоров и в некоторых режимах компиляции.

Edenia 28.05.2018 05:23

Это неплохо, и это не больно, сильно, это просто бессмысленно, если у вас нет большого количества их рядом, например, g. в массиве, или какой-то сумасшедший объект с сотнями смежных членов short. Или, если вы по какой-то причине специально выполняете 16-битную арифметику, например преобразование системы счисления. Конечно, бывают ситуации, когда это единственно правильный выбор.

user207421 28.05.2018 05:26

@Edenia: Что вы имеете в виду под «определенными преимуществами»?

Nicol Bolas 28.05.2018 05:41

@NicolBolas Преимущества, которые когда-либо описывались или упоминались в каком-то надежном источнике (руководство по компилятору, создатель языка, Джон Скит, я не знаю) ..

Edenia 28.05.2018 05:43

обман из Когда использовать short вместо int? и, возможно, больше

underscore_d 28.05.2018 08:49

Не используйте ни то, ни другое. Используйте int_fast8_t и int_fast16_t, если вам нужно работать только с меньшими числами. Однако они (как char и short) подчиняются тупо опасным правилам неявного целочисленного продвижения C и C++, что на самом деле является основной причиной по возможности избегать небольших целочисленных типов.

Lundin 28.05.2018 10:11

См. Вопрос Правила повышения неявного типа для некоторых примеров, когда эти типы стреляют вам в ногу.

Lundin 28.05.2018 10:12

@Lundin Чем опасны целочисленные рекламные акции?

PSkocik 28.05.2018 14:25

@PSkocik Ссылка, которую я разместил выше, дает несколько примеров для иллюстрации.

Lundin 28.05.2018 15:14

@Lundin Спасибо.

PSkocik 28.05.2018 15:33
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
11
14
956
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Как правило, большая часть арифметических операций на языке C выполняется с использованием типа int (то есть простого int, а не short или long). Это потому, что (а) так сказано в определении C, что связано с тем, что (б) именно так предпочитают работать многие процессоры (по крайней мере, те, которые имели в виду разработчики C).

Поэтому, если вы попытаетесь «сэкономить место», используя вместо этого целые числа short, и напишете что-то вроде

short a = 1, b = 2;
short c = a + b;

компилятор должен выдать код, чтобы фактически преобразовать a из short в int, преобразовать b из short в int, выполнить сложение и преобразовать сумму обратно в short. Возможно, вы сэкономили немного места в хранилище для a, b и c, но ваш код, вероятно, будет больше (и медленнее).

Если вы вместо этого напишите

int a = 1, b = 2;
int c = a + b;

вы тратите немного больше места для хранения в a, b и c, но код, вероятно, меньше и быстрее.

Это несколько упрощенный аргумент, но он стоит за вашим наблюдением, что использование типа short редко, а обычный int обычно рекомендуется. По сути, поскольку это «естественный» размер машины, предполагается, что это наиболее простой тип для выполнения арифметических операций без дополнительных преобразований в и из менее естественных типов. Это своего рода аргумент «В Риме, делай, как римляне», но обычно делает делает использование простого int более выгодным.

Если у вас есть лоты не очень больших целых чисел для хранения, с другой стороны (большой их массив или большой массив структур, содержащих не очень большие целые числа), экономия хранилища для данных может быть большой, и стоит того, чтобы пожертвовать (относительно меньшим) увеличением размера кода и потенциальным увеличением скорости.

См. Также этот предыдущий вопрос SO и эта запись в списке часто задаваемых вопросов C.


Дополнение: как и любая проблема оптимизации, если вы действительно заботитесь об использовании пространства данных, использовании пространства кода и скорости кода, вы захотите провести тщательные измерения, используя свой точный компьютер и процессор. В конце концов, вашему процессору могут не потребоваться какие-либо «дополнительные инструкции преобразования» для преобразования в / из меньших типов, поэтому их использование может быть не таким большим недостатком. Но в то же время вы, вероятно, можете подтвердить, что для изолированных переменных их использование также может не дать какого-либо измеримого преимущества.


Приложение 2. Вот данные. Я экспериментировал с кодом

extern short a, b, c;

void f()
{
    c = a + b;
}

Я скомпилировал с помощью двух компиляторов, gcc и clang (компиляция для процессора Intel на Mac). Затем я заменил short на int и снова скомпилировал. Код, использующий int, был на 7 байт меньше в gcc и на 10 байт меньше в clang. Изучение вывода на языке ассемблера позволяет предположить, что разница заключалась в усечении результата, чтобы сохранить его в c; выборка short в отличие от int, похоже, не меняет счетчика команд.

Однако затем я попробовал вызов в двух разных версиях и обнаружил, что это практически не влияет на время выполнения даже после 10000000000 вызовов. Таким образом, «использование short может увеличить размер кода», часть ответа подтверждается, но, возможно, нет, «а также делает его медленнее».

В этом есть смысл, я знал взаимосвязь между пространством и производительностью. Кэширование - это наиболее общий способ повышения производительности, а синтаксический анализ - для уменьшения потребления места.

Edenia 28.05.2018 05:34

Определенное преимущество @Edenia Type int состоит в том, что он считается наиболее "естественным" и эффективным типом для выполнения арифметических операций. Любой другой, менее "естественный" тип может потребовать дополнительных преобразований или менее эффективной арифметики. Я думаю, вы это знали, поэтому, если вы спрашиваете, есть ли у Другие менее очевидные преимущества, я думаю, ответ будет: «Нет, просто« естественность »».

Steve Summit 28.05.2018 05:46

Да, хорошо, я удивлен, почему не многие люди задаются вопросом, почему все используют целые числа в книгах, учебных пособиях, проектах с открытым исходным кодом, в то время как существуют другие типы данных, предназначенные для более низких значений. Для языков с более высокой абстракцией, таких как Object Pascal, это еще более очевидно.

Edenia 28.05.2018 05:49

@Edenia, потому что диапазон значений, которые могут храниться в целочисленных типах, не применяется во время выполнения. Так что в абстракции нет преимущества.

Richard Critten 28.05.2018 10:35

Большинство так называемых дополнительных инструкций, упомянутых здесь, встроены в процессоры и не отличаются по размеру или количеству от кода, который будет выдан для int. Например, загрузка 32-битного регистра из 16-битного места с расширением знака или наоборот с усечением высокого порядка.

user207421 28.05.2018 10:37

@EJP Верно. Вот почему я сказал: «Это в некоторой степени упрощенный аргумент», и почему я вставил «предположительно» в «предполагается, что это самый простой тип для арифметических операций», и почему я вставил «обычно» в «обычно» действительно делает использование обычного int выгодным ». Но я должен добавить еще несколько слов по этим строкам; Спасибо за напоминание.

Steve Summit 28.05.2018 13:15

А? Вы заявили без оговорок, что «комплилер должен выдавать код» и «ваш код больше и медленнее». Я не занимался этим серьезно тридцать лет, но тогда это было неправдой, и это неправда сейчас.

user207421 28.05.2018 13:28

@EJP Ну, я тоже занимаюсь этим уже не менее 30 лет, хотя я и не следовал своему собственному совету, поскольку я никогда не проводил никаких тщательных измерений, чтобы подтвердить эффект. (Я просто повторял партийную линию, и это одна из причин, по которой я сказал: «Находясь в Риме, делайте, как римляне».) Но я только что выполнил несколько измерений, и действительно, используя shortделает, сделал код больше . Скоро опубликую результаты.

Steve Summit 28.05.2018 13:58

Я скептически отнесся к утверждению, что короткий код должен быть медленнее и больше в любом значительном смысле (при условии, что здесь локальные переменные, никаких споров о больших массивах, где short определенно окупаются, если это необходимо), поэтому я попытался заменить его на моем Intel(R) Core(TM) i5 CPU M 430 @ 2.27GHz

Я использовал (long.c):

long long_f(long A, long B)
{
    //made up func w/ a couple of integer ops 
    //to offset func-call overhead
    long r=0;
    for(long i=0;i<10;i++){
        A=3*A*A;
        B=4*B*B*B;
        r=A+B;
    }
    return r;
}

в версии на основе long, int и short (%s/long/TYPE/g) построил программу с gcc и clang в -O3 и -Os и измерил размеры и время выполнения для 100 мил запусков каждой из этих функций.

f.h:

#pragma once
int int_f(int A, int B);
short short_f(short A, short B);
long long_f(long A, long B);

main.c:

#include "f.h"
#include <stdlib.h>
#include <stdio.h>
#define CNT 100000000
int main(int C, char **V)
{
    int choose = atoi(V[1]?:"0");
    switch(choose){
    case 0:
        puts("short");
        for(int i=0; i<CNT;i++)
            short_f(1,2);
        break;
    case 1:
        puts("int");
        for(int i=0; i<CNT;i++)
            int_f(1,2);
        break;
    default:
        puts("long");
        for(int i=0; i<CNT;i++)
            long_f(1,2);
    }
}

строить:

#!/bin/sh -eu
time(){ command time -o /dev/stdout "$@"; }
for cc in gcc clang; do
    $cc -Os short.c -c
    $cc -Os int.c -c
    $cc -Os long.c -c
    size short.o int.o long.o
    $cc main.c short.o int.o long.o

    echo $cc -Os
    time ./a.out 2
    time ./a.out 1
    time ./a.out 0

    $cc -O3 short.c -c
    $cc -O3 int.c -c
    $cc -O3 long.c -c
    size short.o int.o long.o
    $cc main.c short.o int.o long.o
    echo $cc -O3
    time ./a.out 2
    time ./a.out 1
    time ./a.out 0
done

Я проделал это дважды, и результаты оказались стабильными.

   text    data     bss     dec     hex filename
     79       0       0      79      4f short.o
     80       0       0      80      50 int.o
     87       0       0      87      57 long.o
gcc -Os
long
3.85user 0.00system 0:03.85elapsed 99%CPU (0avgtext+0avgdata 1272maxresident)k
0inputs+0outputs (0major+73minor)pagefaults 0swaps
int
4.78user 0.00system 0:04.78elapsed 99%CPU (0avgtext+0avgdata 1220maxresident)k
0inputs+0outputs (0major+74minor)pagefaults 0swaps
short
3.36user 0.00system 0:03.36elapsed 99%CPU (0avgtext+0avgdata 1328maxresident)k
0inputs+0outputs (0major+74minor)pagefaults 0swaps
   text    data     bss     dec     hex filename
    137       0       0     137      89 short.o
    109       0       0     109      6d int.o
    292       0       0     292     124 long.o
gcc -O3
long
3.90user 0.00system 0:03.90elapsed 99%CPU (0avgtext+0avgdata 1220maxresident)k
0inputs+0outputs (0major+74minor)pagefaults 0swaps
int
1.22user 0.00system 0:01.22elapsed 99%CPU (0avgtext+0avgdata 1260maxresident)k
0inputs+0outputs (0major+73minor)pagefaults 0swaps
short
1.62user 0.00system 0:01.62elapsed 99%CPU (0avgtext+0avgdata 1228maxresident)k
0inputs+0outputs (0major+73minor)pagefaults 0swaps
   text    data     bss     dec     hex filename
     83       0       0      83      53 short.o
     79       0       0      79      4f int.o
     88       0       0      88      58 long.o
clang -Os
long
3.33user 0.00system 0:03.33elapsed 99%CPU (0avgtext+0avgdata 1316maxresident)k
0inputs+0outputs (0major+71minor)pagefaults 0swaps
int
3.02user 0.00system 0:03.03elapsed 99%CPU (0avgtext+0avgdata 1316maxresident)k
0inputs+0outputs (0major+71minor)pagefaults 0swaps
short
5.27user 0.00system 0:05.28elapsed 99%CPU (0avgtext+0avgdata 1236maxresident)k
0inputs+0outputs (0major+69minor)pagefaults 0swaps
   text    data     bss     dec     hex filename
    110       0       0     110      6e short.o
    219       0       0     219      db int.o
    279       0       0     279     117 long.o
clang -O3
long
3.57user 0.00system 0:03.57elapsed 99%CPU (0avgtext+0avgdata 1228maxresident)k
0inputs+0outputs (0major+69minor)pagefaults 0swaps
int
2.86user 0.00system 0:02.87elapsed 99%CPU (0avgtext+0avgdata 1228maxresident)k
0inputs+0outputs (0major+68minor)pagefaults 0swaps
short
1.38user 0.00system 0:01.38elapsed 99%CPU (0avgtext+0avgdata 1204maxresident)k
0inputs+0outputs (0major+70minor)pagefaults 0swaps

Результаты довольно близки, но они довольно сильно различаются в зависимости от разных компиляторов и настроек компилятора.

Я пришел к выводу, что выбор между int и short в теле функции или сигнатуре (массивы - другая проблема), потому что один должен работать лучше другого или генерировать более плотный код, в основном бесполезен (по крайней мере, в коде, который не привязан к конкретному компилятор с определенными настройками). Либо быстро, поэтому я бы выбрал тот тип, который лучше соответствует семантике моей программы или лучше передает мой API. (Если я ожидаю короткого положительного значения, можно также использовать в подписи uchar или ushort.)

Программисты на C предрасположены использовать int, потому что C исторически их одобрял (целочисленные литералы имеют тенденцию быть int, рекламные акции имеют тенденцию создавать int, раньше были неявные правила int для объявлений и необъявленных функций и т. хорошо подходит для архитектуры, но, в конце концов, важен плотный, производительный машинный код с читаемым, поддерживаемым исходным кодом, и если ваша теория для выполнения чего-либо в исходном коде явно не способствует хотя бы одному из этих голы, думаю, это плохая теория.

Если вы сравните эти типы на микроконтроллерной системе, разница будет очень очевидна. В среднем 8-битный unsigned char может дать одну инструкцию, тогда как unsigned short дает от 5 до 10 инструкций, а unsigned long - около 100 инструкций.

Lundin 28.05.2018 15:38

@Lundin звучит как еще одна причина использовать наиболее подходящий тип вместо int

PSkocik 28.05.2018 15:41

Также enums неявно запрашиваются у int.

Edenia 28.05.2018 16:25

Здесь есть несколько проблем.

  • Во-первых, тип char совершенно не подходит для хранения целочисленных значений. Его следует использовать только для удержания персонажей. Это связано с тем, что он имеет подпись, определяемую реализацией, char фактически является отдельным типом, отдельным от signed char и unsigned char. См. По умолчанию char подписан или неподписан?.

  • Однако основная причина, по которой следует избегать использования небольших целочисленных типов, таких как char и short, по возможности, заключается в неявном продвижении типов. Эти типы подлежат целочисленному продвижению, что, в свою очередь, может привести к опасным вещам, таким как тихая смена подписи. Подробнее см. Правила повышения неявного типа.

    По этой причине некоторые стандарты кодирования прямо запрещают использование целочисленных типов меньшего размера. Хотя для того, чтобы такое правило было осуществимо, вам понадобится 32-битный процессор или больше. Так что это не совсем хорошее универсальное решение, если нужно учитывать разные микроконтроллеры.

    Также обратите внимание, что такое микроуправление памятью в основном актуально только при программировании встроенных систем. Если вы программируете программы для ПК, использование меньших типов для экономии памяти, вероятно, является «преждевременной оптимизацией».

  • Стандартные "примитивные типы данных" языка C, включая char, short, int, в целом не переносимы. Они могут меняться в размере при переносе кода, что, в свою очередь, придает им недетерминированное поведение. Кроме того, C допускает всевозможные неясные и экзотические форматы подписи для этих типов, такие как дополнение, знак и величина, биты заполнения и т. д.

    Надежный, переносимый и качественный код вообще не использует эти типы, а использует типы stdint.h. В качестве бонуса эта библиотека допускает только нормальное отраслевое дополнение до двух.

  • Использование меньших целочисленных типов для экономии места не является хорошей идеей по всем вышеупомянутым причинам. Опять же, stdint.h предпочтительнее. Если вам нужен универсальный тип, который переносимо экономит память, если экономия памяти не означает снижение скорости выполнения, используйте int_fast8_t и аналогичные. Это будет 8 бит, если использование большего типа не означает более быстрое выполнение.

Другие вопросы по теме