LINUX.ORG.RU

Использование const в аргументах функции в сях


0

1

Такой вопрос: в чем принципиальная разница между

void func(int arg)
и
void func(const int arg)
? Ведь во втором случае, получается, единственное чего нельзя делать - это использовать arg в качестве изменяемой локальной переменной. Но все равно ведь можно сделать так:
void func(const int arg) {
    int arg_local = arg;
    arg_local++;
    ...
}

Была мысль, что в первом случае нельзя сделать такой вызов:

func(5)
то есть подать на вход константу. Но gcc даже warning-ов не выдает. Так все же: имеет ли какой-то смысл использование const для стандартных типов в аргументах функции при передаче по значению (т. е. без использования указателей)?

★★★

Последнее исправление: Kiborg (всего исправлений: 1)

Ответ на: комментарий от Kiborg

разница в том, что

void func(const int arg)
{
   arg = 0; // <-- ошибка
}

а

void func(int arg)
{
   arg = 0; // <-- не ошибка
}

больше сходу не могу ничего придумать.

waker ★★★★★
()
Ответ на: комментарий от panter_dsd

Во-первых, ссылок в сях нет, а во-вторых ссылки в C++ по определению константные - в том смысле что изменить адрес, на который они указывают, нельзя. Другое дело что объект, на который они ссылаются, тоже можно сделать константным: const int &arg. Но это только вопрос терминологии.

Kiborg ★★★
() автор топика
Ответ на: комментарий от waker

Ну я про это и сказал:

единственное чего нельзя делать - это использовать arg в качестве изменяемой локальной переменной

Kiborg ★★★
() автор топика

Для самих переменных, будь то данные (1) или указатели (2), ставить конст нужно только, если хочешь неизменности входных переменных. Имеет практический смысл ставить конст на данные, передаваемые по указателю (3). В С++ это расширяется и на ссылки.

(1) void foo(int const x){}

(2) void foo(int *const x){}

(3) void foo(int const *x){}
staseg ★★★★★
()

имеет ли какой-то смысл использование const для стандартных типов в аргументах функции при передаче по значению (т. е. без использования указателей)?

Не имеет никакого.

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

В Си const нужен только для того, чтобы поместить статические данные в р/о сегмент данных.

LamerOk ★★★★★
()
Ответ на: комментарий от staseg

если хочешь неизменности входных переменных

Так ведь при передаче по значению это и без const гарантируется: за счет создания локальной копии переменной (случаи 1 и 2). Т. е. когда я делаю

void foo(int const x){}
foo(myownx);
создается локальная для foo копия переменной myownx, как если бы в коде функции было написано
int const x = myownx

Kiborg ★★★
() автор топика
Ответ на: комментарий от LamerOk

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

Kiborg ★★★
() автор топика
Ответ на: комментарий от Kiborg

Я имел в виду то, что ты назвал так:

Ведь во втором случае, получается, единственное чего нельзя делать - это использовать arg в качестве изменяемой локальной переменной.

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

void foo(int const &x){
 print(x);
 ...
 print(x);
}

Тут у тебя несколько выше шансы в конце функции получить оригинальные входные данные, во всяком случае ты убережешь себя от прямой их модификации x=42, memset(&x,..) и т.д.

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

staseg ★★★★★
()
Ответ на: комментарий от staseg

А, тогда да.

Отметил тему решенной, вроде бы выяснили что const при передаче по значению не особо нужен.

Kiborg ★★★
() автор топика

Ни разу не пользовался const в аргументах функций и не вижу необходимости использовать его где бы то ни было

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от Kiborg

Почему компилятор не может сделать так? const у аргумента могло бы служить подсказкой.

static int _a, _b;

_foo (int _c)
{
  return _c ? _foo (_c - 1) : bar (a, b);
}

foo (const int a, const int b, int c)
{
  _a = a;
  _b = b;

  return _foo (c);
}

backbone ★★★★★
()
Ответ на: комментарий от LamerOk

ну когда кто-то пишет функцию навроде

func_write_buffer_to_file(char *buffer,char *filename);
а ее потом вызывают
func_write_buffer_to_file(buf,"/home/user/bufferdump.bin")

Kiborg ★★★
() автор топика
Ответ на: комментарий от backbone

Уж слишком нетривиальный частный случай, который практически никакого выигрыша в работе не дает, мне кажется. Сомневаюсь, чтобы что-то подобное было хотя бы в -O3.

На самом деле даже для меня const в этом случае подсказкой не стал :) И хотя там можно еще дооптимизировать, все же это уже больше работа программиста, чем компилятора.

Kiborg ★★★
() автор топика
Ответ на: комментарий от backbone

Хотя, почему «даже».. Сейчас компиляторы серьезно оптимизировать умеют. Но все-таки не верю я в оптимизирующую силу конста :)

Kiborg ★★★
() автор топика

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

AndrewMT
()
Ответ на: комментарий от Eddy_Em

Не скажите )))) Ведь можно и случайно «накосячить», изменить объект... А так - защита от лишней работы по поиску ошибок. Тем более, если с кодом потом будет работать другой программер.

AndrewMT
()
Ответ на: комментарий от Kiborg

в Си в этом случае никаких предупреждений нет, литеральная строка - это char *, а не const char *. Вот в плюсах - будет.

LamerOk ★★★★★
()
Ответ на: комментарий от backbone

Почему компилятор не может сделать так? const у аргумента могло бы служить подсказкой.

Ага, то-то радости в многопоточной программе будет от таких «оптимизаций», ухихикаться можно просто.

kemm
()
Ответ на: комментарий от Eddy_Em

Необходимости действительно нет, новых возможностей const не дает. Но зато отбирает потенциально опасные.

note173 ★★★★★
()
Ответ на: комментарий от Eddy_Em

О да, про таких как ты я говорю «он на любом языке напишет программу на Фортране» (77, для непонятливых, чья убогость уже притча во языцех)

unanimous ★★★★★
()
Ответ на: комментарий от LamerOk

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

глупости. на libc функции посмотри. просто const в сях появился не сразу, видимо работаешь с какими то древними сырцами, раз выдвинул такое умозаключение

vvviperrr ★★★★★
()
Ответ на: комментарий от waker

Может пригодится в работе с opengl. Если имеется взвод code monkeys и рота индусов, то ставим чёткий гайдлайн - все передаваемые дескрипторы файлов unix или дескрипторы opengl передаём как const, хоть и по значению. И имеем чуть больше уверенности в отсутствии ошибок.

anonymous
()
Ответ на: комментарий от backbone

А ещё __declspec(thread) и thread_local. Write once, #ifdef anywhere.

anonymous
()
Ответ на: комментарий от vvviperrr

глупости. на libc функции посмотри.

Глупости. Посмотри на реальные сишные проекты, а не на сигнатуры функций из стандартной библиотеки.

LamerOk ★★★★★
()
Ответ на: комментарий от anonymous

все передаваемые дескрипторы файлов unix или дескрипторы opengl передаём как const,

И что это даст?

LamerOk ★★★★★
()
Ответ на: комментарий от LamerOk

Глупости. Посмотри на реальные сишные проекты, а не на сигнатуры функций из стандартной библиотеки.

Дураков полно. Учиться нужно у умных.

anonymous
()
Ответ на: комментарий от anonymous

Эти «дураки» сделали весь софт, которым ты и все остальные обладатели чего-либо сложнее калькулятора пользуются, даже если не знают этого.

LamerOk ★★★★★
()
Ответ на: комментарий от r2d2

Альтернативно-одарённые товарищи из gcc в 4.0 (кажется) вынесли этот варнинг мало того, что из -Wall, так и из -Wextra (видите ли, криворучки их жалобами задолбали), так что теперь приходится ещё лепить -Wwrite-strings. Некоторые просто этого не застали. 8))

Хотя предупреждение безусловно полезное, падения при попытке изменить строковый литерал никто не отменял, да (-fwriteable-strings вынесли тоже примерно тогда же, но это как раз была правильная идея).

kemm
()
Ответ на: комментарий от LamerOk

Тебе это только кажется. Весь вменяемый софт написан нормально, от библиотек начиная. Более того, достаточно увидеть что-нибудь типа 'int cool_sprintf(char *buf, char *fmt. ...)' — к гадалке не ходи, писано левым задним копытом, а не руками.

kemm
()
Ответ на: комментарий от backbone

А так тебе никто не гарантирует, что в foo() никто не делал диверсии типа 'bar((int *)&a);'

BTW, clang с const-оптимизациями намудрил.

#include <stdio.h>

static void
bar(int *n)
{
    *n = *n - 1;
}

int
main()
{
    const int n = 18;
    while (n > 0)
        bar((int *)&n);

    printf("%d\n", n);
    return 0;
}

Бесконечный цикл даже с -O0, получите и распишитесь.

kemm
()
Ответ на: комментарий от kemm

gcc, к слову сказать, с -O0 таки выполняет правильно, оптимизирует данный момент только с -O1 и дальше.

kemm
()
Ответ на: комментарий от kemm

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

LamerOk ★★★★★
()
Ответ на: комментарий от kemm

видите ли, криворучки их жалобами задолбали

Это не «криворучки», а овер 9к софта, где литералы шли не как конст.

LamerOk ★★★★★
()
Ответ на: комментарий от LamerOk

Это — криворучки. А over 9k криворучек стало как раз после отмены этого варнинга, до сих пор вылезают падения иногда при попытке где-нибудь унутрях libc поменять литерал, переданный туда через 15 функций, все из которых с аргументами 'char *'

kemm
()
Ответ на: комментарий от LamerOk

const по отношению к указателям действительно используется в некоторых хороший проектах

Пожалуйста, не смотри больше на говно, а то у тебя создаётся превратное представление про хорошие проекты. Если функция не меняет объект — она обязана иметь аргументом 'const object_type *', и никак иначе. В противном случае это фиговый код, а не «ненекоторый хороший проект»

kemm
()

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

Разумное зерно в этих рассуждениях есть, но реально это порождает слишком много лишних токенов и усложняет чтение программы. Было бы неплохо, если бы в С параметры функции были бы константными по умолчанию, а какое-нибудь ключевое слово вроде var делало бы их неконстантными, но этого нет. А опечатки, которые хотят отлавливать const-ами встречаются достаточно редко, чтобы выгода от их раннего обнаружения превысила вред от раздутой программы.

К оптимизации const не может иметь никакого отношения хотя бы потому, что компилятор видит весь текст функции и сам способен легко определить, что переменной в функции ничего не присваивается/она не передаётся в качестве неконстантного указателя.

Legioner ★★★★★
()
Ответ на: комментарий от Legioner

Это всё касается сигнатур типа const int, или char * const. Сигнатура const char * означает совсем другое, что функция не меняет значение по указателю, и, естественно, используется очень часто и к предыдущим моим рассуждениям отношения не имеет.

Legioner ★★★★★
()
Ответ на: комментарий от kemm

Пожалуйста, не смотри больше на говно, а то у тебя создаётся превратное представление про хорошие проекты. Если функция не меняет объект — она обязана иметь аргументом 'const object_type *', и никак иначе. В противном случае это фиговый код, а не «ненекоторый хороший проект»

Orly?

Берём наугад функцию из sqlite.

SQLITE_API int sqlite3_bind_text(sqlite3_stmt*, int, const char*, int n, void(*)(void*));

...

static
int registerTokenizer(
  sqlite3 *db, 
  char *zName, 
  const sqlite3_tokenizer_module *p
){
  int rc;
  sqlite3_stmt *pStmt;
  const char zSql[] = "SELECT fts3_tokenizer(?, ?)";

  rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
  if( rc!=SQLITE_OK ){
    return rc;
  }

  sqlite3_bind_text(pStmt, 1, zName, -1, SQLITE_STATIC);
  sqlite3_bind_blob(pStmt, 2, &p, sizeof(p), SQLITE_STATIC);
  sqlite3_step(pStmt);

  return sqlite3_finalize(pStmt);
}

Где по zName потенциально могут измениться данные? Если нигде, то следует ли из этого, что sqlite - говно?

А вот чем обходятся консты в реальной практике:

SQLITE_PRIVATE const char *sqlite3Fts3NextToken(const char *zStr, int *pn);

...

SQLITE_PRIVATE int sqlite3Fts3InitTokenizer(
  Fts3Hash *pHash,                /* Tokenizer hash table */
  const char *zArg,               /* Tokenizer name */
  sqlite3_tokenizer **ppTok,      /* OUT: Tokenizer (if applicable) */
  char **pzErr                    /* OUT: Set to malloced error message */
){
  int rc;
  char *z = (char *)zArg;
  int n;
  char *zCopy;
  char *zEnd;                     /* Pointer to nul-term of zCopy */
  sqlite3_tokenizer_module *m;

  zCopy = sqlite3_mprintf("%s", zArg);
  if( !zCopy ) return SQLITE_NOMEM;
  zEnd = &zCopy[strlen(zCopy)];

  z = (char *)sqlite3Fts3NextToken(zCopy, &n);
  z[n] = '\0';
  sqlite3Fts3Dequote(z);

  m = (sqlite3_tokenizer_module *)sqlite3Fts3HashFind(pHash,z,(int)strlen(z)+1);
  if( !m ){
    *pzErr = sqlite3_mprintf("unknown tokenizer: %s", z);
    rc = SQLITE_ERROR;
  }else{
    char const **aArg = 0;
    int iArg = 0;
    z = &z[n+1];
    while( z<zEnd && (NULL!=(z = (char *)sqlite3Fts3NextToken(z, &n))) ){
      int nNew = sizeof(char *)*(iArg+1);
      char const **aNew = (const char **)sqlite3_realloc((void *)aArg, nNew);
      if( !aNew ){
        sqlite3_free(zCopy);
        sqlite3_free((void *)aArg);
        return SQLITE_NOMEM;
      }
      aArg = aNew;
      aArg[iArg++] = z;
      z[n] = '\0';
      sqlite3Fts3Dequote(z);
      z = &z[n+1];
    }
    rc = m->xCreate(iArg, aArg, ppTok);
    assert( rc!=SQLITE_OK || *ppTok );
    if( rc!=SQLITE_OK ){
      *pzErr = sqlite3_mprintf("unknown tokenizer");
    }else{
      (*ppTok)->pModule = m; 
    }
    sqlite3_free((void *)aArg);
  }

  sqlite3_free(zCopy);
  return rc;
}

LamerOk ★★★★★
()
Ответ на: комментарий от kemm

Это — криворучки.

Это - код, написанный задолго до С88/89, и даже после, для платформ, под которых стандартного компилятора просто могло не быть.

А over 9k криворучек стало как раз после отмены этого варнинга ...

Овер 9к «криворучек» жило и работало до выхода стандарта и появления совместимых с ним компиляторов. Даже код начала 90-ых частенько мог иметь K&R синтаксис.

LamerOk ★★★★★
()
Ответ на: комментарий от LamerOk

я и смотрю на реальные сишные проекты (git, vim, mpd, linux).

vvviperrr ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.