Comments 57
uint32_t hello[const]
— эта конструкция объявляет константный указатель на массив. Элементы hello[]
менять можно, а сам hello
нет.Только с ходу не могу придумать для него область применения. Но все равно интересно
Еще из «внутрискобочных» объявлений есть
foo[static 5]
, позволяет сказать компилятору, что в массиве есть не менее 5 элементов.A declaration of a parameter as ‘‘array of type’’ shall be adjusted to ‘‘qualified pointer to type’’, where the type qualifiers (if any) are those specified within the [ and ] of the array type derivation. If the keyword static also appears within the [ and ] of the array type derivation, then for each call to the function, the value of the corresponding actual argument shall provide access to the first element of an array with at least as many elements as specified by the size expression.
Можно, например, объявлять глобальные константные массивы
Нельзя. Эта конструкция может использоваться только как параметр в прототипе или определении функции.
uint32_t hello[const] — эта конструкция объявляет константный указатель на массив.
Эта конструкция объявляет массив, а не указатель, согласно c99 6.7.5.2:3.
const uint64_t *bob… это «неизменяемый указатель на неизменяемые данные».не верно, это всего лишь «изменяемый указатель на неизменяемые данные».
Главная причина в том, что если тип — не указатель, то никаких оптимизаций и не применишь — чтение и запись происходят явно, а если указатель, то неизвестно, кто ещё ссылается на эту же область памяти (см. aliasing). В C99 добавили restrict, который помогает компилятору, но "мешает" пользователю в том плане, что пользователь должен гарантировать отсутствие алиасинга для данного указателя, иначе "я за себя не отвечаю, братцы".
const
может влиять на генерируемый код.$ cat test-int.c
extern int x;
extern int foo(int);
int bar() {
int y = foo(x);
return x + y;
}
$ gcc -O3 -S -o- test-int.c
.file "test-int.c"
.section .text.unlikely,"ax",@progbits
.LCOLDB0:
.text
.LHOTB0:
.p2align 4,,15
.globl bar
.type bar, @function
bar:
.LFB0:
.cfi_startproc
subq $8, %rsp
.cfi_def_cfa_offset 16
movl x(%rip), %edi
call foo
addl x(%rip), %eax
addq $8, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE0:
.size bar, .-bar
.section .text.unlikely
.LCOLDE0:
.text
.LHOTE0:
.ident "GCC: (GNU) 5.3.0"
.section .note.GNU-stack,"",@progbits
$ cat test-constint.c
extern const int x;
extern int foo(int);
int bar() {
int y = foo(x);
return x + y;
}
$ gcc -O3 -S -o- test-constint.c
.file "test-constint.c"
.section .text.unlikely,"ax",@progbits
.LCOLDB0:
.text
.LHOTB0:
.p2align 4,,15
.globl bar
.type bar, @function
bar:
.LFB0:
.cfi_startproc
pushq %rbx
.cfi_def_cfa_offset 16
.cfi_offset 3, -16
movl x(%rip), %ebx
movl %ebx, %edi
call foo
addl %ebx, %eax
popq %rbx
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE0:
.size bar, .-bar
.section .text.unlikely
.LCOLDE0:
.text
.LHOTE0:
.ident "GCC: (GNU) 5.3.0"
.section .note.GNU-stack,"",@progbits
То что в простых случаях компилятор может сгенерировать одинаковый код вовсе не говорит о том, что так будет всегда.
Для avr (avr-gcc) const не разместит в flash, т.к. для чтения нужно будет обращаться не b[i], а pgm_read_byte(b[i]);
Не соглашусь только с
В Гарвардской архитектуре (большинство микроконтроллеров)
Сейчас большинство контроллеров с modified harvard, который является средним между чистым гарвардом и фон-нейманном (т. е. единое пространство памяти, но независимые кэши для данных и инструкций. Это относится к современным arm, avr32.
Бывает более слабо модифицированный гарвард представлен у avr и pic, где адресные пространства разные, но при этом есть инструкции для чтения и модификации памяти инструкций.
Ещё бывают 8051/8052 с чистой фон-нейманновской архитектурой (в частности, при использовании внешней памяти), arm7, msp430.
void foo(const int a, const double b)
влияет ли это на оптимизацию?
Я слышал разные мнения.
А мне вот интересно, регламентируется ли как-то стандартом размещение в памяти частично неизменяемых структур? Может ли оптимизатор выкидывать неименяемые члены, заменяя их на константы? И если нет, зачем вообще в практическом плане такие структуры могут потребоваться?
Если имеется в виду одна и та же константа, разделённая между всеми структурами, то пишут struct {static const int c;}, но это вроде как C++ only
Ну как же неизвестно. При создании структуры нам же по любому инициализировать эти поля придётся, даже если в разных структурах значения констант будут разными, они же всё равно будут известны компилятору. А после инициализации изменить их уже нельзя.
P.S. Интересно — есть ли какой-нибудь подобный сайт посвящённый именно языку C, а не C++? Всё-таки это несколько разные языки (хотя вот конкретно эта часть одинакова).
Так в том то и дело, что в данной ситуации компилятор может понять, какой формат структуры во всех случаях использования этой самой структуры :) Он может, например, выкидывать константные поля и offsetof
будет просто возвращать смещение, как будто бы этих полей в принципе в структуре нет. Если в стандарте не закреплено определённое размещение структуры с константными полями, кто ему мешает проводить такую оптимизацию? Вот мне бы и хотелось услышать от знающих людей, что по этому поводу говорит стандарт.
Так в том то и дело, что в данной ситуации компилятор может понять, какой формат структуры во всех случаях использования этой самой структуры :)Это, я извиняюсь, как?
Рассмотрим простейший пример.
Библитека:
struct Serializer {
const int version;
int offset;
int counter;
...
};
int Serialize(struct Serializer* serializer) {
... используем serializer->version ...
}
Программа:
struct Serializer g_serializer = { 10 };
...
Serialize(&g_serializer)
...
Как компилятор может при сборке библиотеки догадаться что будет в поле
version
, если главная программа ещё даже не спроектирована в момент сборки это библиотеки? Хрустальным шаром, позволяющим предсказывать будущее, компьютеры пока не комплектуются…Вот мне бы и хотелось услышать от знающих людей, что по этому поводу говорит стандарт.А чего он, собственно, может сказать? Константные поля — это такие же поля, как и любые другие. Указатель на них можно передать куда-нибудь, где вообще не будет известно, что это поля структуры. Так что в общем случае ничего с этим поделать нельзя.
В частном случае — можно: увидев, что, скажем, структура «за пределы функции» не выходит компилятор может её вообще извести, оставив от неё отдельные поля — но это от константности не зависит: в точности то же самое компилятор и с обычной структурой может проделать.
О! Это именно то, что я хотел увидеть. Я просто не учёл, что в библиотеке структура вообще может никогда не создаваться, а использоваться только через указатель.
Кстати, ваш пример также показывает случай, когда константный член структуры действительно может быть полезен во вполне боевых условиях, поскольку теперь (в примере) ни пользователь, ни библиотека не смогут затереть номер версии без дополнительных ухищрений :)
Также константные переменные можно использовать для указания числа элементов при объявлении массивов.
По‐моему, когда нет взятия адреса и переменная явно не изменяется, компилятор может это делать и без const. А при объявлении массива можно использовать любые переменные, просто это будет аналогом вызова alloca
:
/// @file test.c
#define U __attribute__((unused))
int main(int argc U, char **argv U, char **environ U)
{
const int x = 5;
char v[x] U;
return 0;
}
% clang -pedantic -std=c99 -Wall -Wextra -Weverything test.c
test.c:6:9: warning: variable length array used [-Wvla]
char v[x] U;
^
1 warning generated.
Видите: написано const, а компилятор считает, что у нас VLA (variable length array). И предупреждение не изменится, если написать static const int x = 5;
. В том числе, если переместить определение x
перед функцией, а не внутри неё. gcc
считает также, только нужно заменить -Weverything
на -Wvla
, т.к. это clang‐специфичная возможность, а -Wvla
ни в -Wall
, ни в -Wextra
не входят.
Если что, для VLA можно использовать любые целочисленные выражения:
/// @file test.c
#include <stdio.h>
#define U __attribute__((unused))
int main(int argc, char **argv U, char **environ U)
{
char v[printf("%x", argc)] U;
return 0;
}
тоже работает, хотя printf()
выдаёт ни разу не константу и вообще результат printf здесь не вычислим на этапе компиляции.
Если что, VLA, как и alloca, относятся к возможностям, которые использовать запрещено во многих проектах. К примеру, Google. Основная претензия: шансы получить stack overflow куда выше, и вы будете получать SEGV (в лучшем случае) вместо NULL на выходе функции; alloca приемлем для малых объёмов памяти, но куда как проще забанить alloca и VLA полностью, чем проконтролировать, что программист не будет злоупотреблять этими возможностями.
А constexpr оптимизируется лучше чем const?
Я понимаю. Вопрос, есть ли смысл (и стоит ли) менять const на constexpr, везде, где возможно или нет?
Правда, грубо говоря, следующая: если замена
const
на constexpr
возможна — то пофиг, что использовать, на генерируемый код это не повляет.Однако есть места, где можно использовать только
const
(например если ваша константа зависит от параметра функции) и есть места, где можно использовать только constexpr
(например если вы хотите параметризовать этой переменной шаблон).Менять ли… я бы сказал, что да, стоит — но без ажиотажа. Выгода не для компилятора, а для программиста: в тех местах где для
const
будет сгенерирован неэффективный код constexpr
выдаст ошибку, что позволит её оперативно заметить и исправить, что, несомненно, полезно, но не настолько, чтобы прям всё бросить и бросаться менять все const
в большом проекте…Правда, грубо говоря, следующая: если замена const на constexpr возможна — то пофиг, что использовать, на генерируемый код это не повляет.
Ну я так и думал если честно.
Менять ли… я бы сказал, что да, стоит — но без ажиотажа.
Я сам вставляю constexpr вместо const и uint32_t вместо int и т.д. Но в чужом коде это редко встречаю — думал может упускаю что-то.
И не забывай проверять constexpr через static_assert. А то маловато смысла писать constexpr там, где на вход функции подается не-constexpr значение
Имелось ввиду семейство типов, используется иногда fast, иногда least.
И не забывай проверять constexpr через static_assert. А то маловато смысла писать constexpr там, где на вход функции подается не-constexpr значение.Какой нафиг «constexpr там, где на вход функции» что-то подаётся? Аргументы функции могут быть
const
, но не constexpr
, а потому вместо static_assert
приходится throw
писать…Отличить один сценарий от второго можно либо просматривая asm-выхлоп, либо пользуясь static-assert'омВ том-то и дело, что никакого
static_assert
'а вам не положено!Так:
constexpr int foo(const int x) {
static_assert(x > 0, "x must be positive!");
return 2 * x;
}
$ g++ -std=c++14 -c test1.cc test1.cc: In function 'constexpr int foo(int)': test1.cc:2:3: error: non-constant condition for static assertion static_assert(x > 0, "x must be positive!"); ^ test1.cc:2:3: error: 'x' is not a constant expression
Так:
constexpr int foo(constexpr int x) {
static_assert(x > 0, "x must be positive!");
return 2 * x;
}
$ g++ -std=c++14 -c test2.cc test2.cc:1:33: error: a parameter cannot be declared 'constexpr' constexpr int foo(constexpr int x) { ^ test2.cc: In function 'constexpr int foo(int)': test2.cc:2:3: error: non-constant condition for static assertion static_assert(x > 0, "x must be positive!"); ^ test2.cc:2:3: error: 'x' is not a constant expression
А вот так:
#include <stdexcept>
constexpr int foo(const int x) noexcept {
return x < 0 ? throw std::logic_error("x must be positive!") :
2 * x;
}
constexpr int i = foo(1);
#ifdef TRY_NEGATIVE
constexpr int j = foo(-1);
#endif
$ g++ -std=c++11 -c test3.cc $ clang++ -std=c++11 -c test3.ccКак видим всё скомпилировалось.
$ g++ -std=c++11 -c -DTRY_NEGATIVE test3.cc test3.cc:10:22: in constexpr expansion of 'foo(-1)' test3.cc:4:62: error: expression '<throw-expression>' is not a constant-expression return x < 0 ? throw std::logic_error("x must be positive!") : ^ $ clang++ -std=c++11 -c -DTRY_NEGATIVE test3.cc test3.cc:10:15: error: constexpr variable 'j' must be initialized by a constant expression constexpr int j = foo(-1); ^ ~~~~~~~ test3.cc:4:18: note: subexpression not valid in a constant expression return x < 0 ? throw std::logic_error("x must be positive!") : ^ test3.cc:10:19: note: in call to 'foo(-1)' constexpr int j = foo(-1); ^ 1 error generated.А тут — не скомпилировлаось.
unit32_t const *const *a;
unit32_t *b = *a;
b = 42;
вызовет ошибку еще на 2 строке
А вот это:
unit32_t const *const *a;
unit32_t *b =(unit32_t) *a;
b = 42;
Однако, ваш компилятор будет жаловаться на несоответствие const для параметров, являющихся указателями или массивами, так как в таком случае ваша функция будет иметь возможность манипулировать данными на которые ссылается передаваемый указатель.
Нет.
void foo(int* a);
void bar(int* const b);
В обоих случаях тип функции — void(int*), именно по той самой причине: указатель нельзя поменять изнутри наружу.
А вот указуемое — тут правда
void foo(int* a);
void buz(int const* b); // void (int const*)
Ну и в принципе логично предположить что первый ваш пример может не подходить под правило из статьи, раз уж там неизменяемыми объявлены данные, а не указатель.
Так что это или корявый перевод, или корявое изложение мысли автором.
Вообще, мотив всей статьи можно было свернуть в одну фразу: «не путайте константность указателя и константность указуемого».
С примечанием «компилятор игнорирует константность аргументов функции».
И со вторым примечанием «компилятор игнорирует в типе функции, но не в её теле»
typedef struct { int x; } foo;
void f(const foo t) { t.x = 1; } /* ошибка, неконстантный доступ к константной переменной */
void g(foo t) { t.x = 1; }
void (*p)(foo) = f; /* ошибки нет, сигнатуры f и g одинаковы */
Так вы думаете, что знаете Const?