LINUX.ORG.RU

По-настоящему важный вопрос

 ,


1

3

Куда вы ставите звёздочки и амперсанды при объявлении/инициализации указателей и ссылок, и почему?

  1. T* name, T& name
  2. T * name, T & name
  3. T *name, T &name
  4. Я талиб, я везде использую передачу по значению

P.S. Это не тупой наброс, мне правда интересно кто как делает, может есть весомые причины делать так или иначе, которые открываются с опытом.

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

Первый. NULL == NULL Откройте .c файл, напишите данное выражение и помедитируйте.

элементарно. вы путаете машинное представление и семантику. так если функции землекоп() и лопата() могут возвратить NULL, это не значит, что выражение семантически корректно

if (землекоп() == лопата()) …

потому что непонятно что вы, как аффтар имели тут ввиду.

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

афтар там еще и разность длинн использует как бул. типа, во как мы знаем си! на самом деле там сравниваются длины, и для этого есть операторы сравнения, а не вычисления разности.

alysnix ★★★
()
Ответ на: комментарий от alysnix
vadim@aquila:/tmp/1$ cat 1.c 
typedef struct {
} zemlekop_t;

typedef struct {
} lopata_t;

extern zemlekop_t* zemlekop();
extern lopata_t* lopata();

int foobar(void)
{
    return zemlekop() == lopata();
}
vadim@aquila:/tmp/1$ gcc -Werror -c 1.c 
1.c: В функции «foobar»:
1.c:12:23: ошибка: сравнение различных указательных типов без приведения типов [-Werror]
   12 |     return zemlekop() == lopata();
      |                       ^~
cc1: все предупреждения считаются ошибками
Status: 1
vadim@aquila:/tmp/1$ 
wandrien ★★
()
Ответ на: комментарий от soomrack

В предыдущем посте не успел написать.

Всё зависит конечно от кода в которой эта функция используется и его действии при возврате ошибки.

К коду придрался лишь из-за того, что бывает «всё с виду правильно».

Forum0888
()
Последнее исправление: Forum0888 (всего исправлений: 1)
Ответ на: комментарий от wandrien

вы мне предложили проверить равенство двух NUL. что мол они равны. да они равны на уровне машинного представления. но это не значит два нула равны по семантике. так что не надо предлагать всякие глупости.

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

Оно используется как функция сравнения для qsort или bsearch? Тогда NULL в качестве какого-либо аргумента там невозможен по определению.

value_size может быть нулевым? Тогда вызов memcmp некорректен.

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

А что непонятного? Впрочем, переполниться может не только вычитание, но и тайпкаст.

Вероятно, value_size там size_t; тогда (int)(1-0x90000000) == 0x70000001 которое больше нуля, хотя должно было быть меньше, или наоборот (int)(0x90000000-1) == -0x70000001 которое меньше нуля когда должно быть больше.

В коде есть явный запрет для value_size иметь такие значения, или это предположение вида «да никто так не будет делать»?

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

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

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

value_size может быть нулевым? Тогда вызов memcmp некорректен.

Корректен. Две строки нулевой длины считаются равными.

memcmp(3):

       If n is zero, the return value is zero.
wandrien ★★
()
Последнее исправление: wandrien (всего исправлений: 1)
Ответ на: комментарий от wandrien

Ну, ладно, видимо всё-таки меньше нуля ему быть надёжно не дадут, тогда (если целое - это int или меньше, не long) переполняться там и правда нечему. Тогда можно вычитать, а результат я бы записал в доп. переменную:

    int diff;
    if(diff=s1->value_size-s2->value_size) return diff;
Это кстати на 6 байт короче чем не сохранять - тоже приятно.

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

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

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

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

Оно используется как функция сравнения для qsort или bsearch? Тогда NULL в качестве какого-либо аргумента там невозможен по определению.

По определению чего?

Это общий compare-метод для ключей. Реализация контейнеров может быть любой. В частности, контейнеры из glib2 не накладывают никаких ограничений на то, что представляет собой ключ, так как работа с ним ведётся через предоставленные вызывающей стороной коллбеки. Хоть NULL там, хоть что, им без разницы.

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

https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf

7.21 String handling <string.h> 7.21.1 String function conventions

Where an argument declared as size_t n specifies the length of the array for a function, n can have the value zero on a call to that function. Unless explicitly stated otherwise in the description of a particular function in this subclause, pointer arguments on such a call shall still have valid values, as described in 7.1.4. On such a call, a function that locates a character finds no occurrence, a function that compares two character sequences returns zero, and a function that copies characters copies zero characters.

Т.е. вернёт оно ноль, но указатель должен быть валидным (который можно разыменовать).

В glibc memcmp объявлен так (__nonnull):

/* Compare N bytes of S1 and S2.  */
extern int memcmp (const void *__s1, const void *__s2, size_t __n)
     __THROW __attribute_pure__ __nonnull ((1, 2));

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

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

Это и есть алгоритм.

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

Оптимизация у меня совсем в другом месте, на тему многопоточности и асинхронной загрузки данных из файловой системы.

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

if(diff=s1->value_size-s2->value_size) return diff;

Выполнять присваивание внутри выражения это плохой стиль.

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

Изменение значения переменной это очень важный участок кода, это настолько важно, что не должно быть спрятано за другими операторами. Настолько важно, что в концепции функционального программирования изменять переменные вообще нельзя.

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

По определению чего?

По определению функций qsort и bsearch.

В частности, контейнеры из glib2 не накладывают никаких ограничений на то, что представляет собой ключ, так как работа с ним ведётся через предоставленные вызывающей стороной коллбеки. Хоть NULL там, хоть что, им без разницы.

Я не знаю, как там что устроено в контейнерах из glib2, но есть подозрение, что с проверкой на NULL тут есть какая-то проблема с логикой.

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

Т.е. вернёт оно ноль, но указатель должен быть валидным (который можно разыменовать).

А как вы себе представляете разыменование указателя на объект нулевой длины, не получив при этом UB?

(У меня не нулевой, так как строки гарантировано нуль-терминированы. Но вообще если отвлечься от данного кода.)

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

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

if (s1->value_size - s2->value_size) ...

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

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

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

А как вы себе представляете разыменование указателя на объект нулевой длины, не получив при этом UB?

Если вы передали указатель в memcmp с n=0, то объект должен быть не нулевой длины, а длины хотя бы 1. Я об этом.

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

самостоятельный тип

А бывают инфантильные?

таких типов в сишечке нет

«Самостоятельных» и «несамостояльных» действительно нет, а вот тип «указатель на int» очень даже есть.

int* a, b;

Даже самые маститые программисты допускают ошибки при дизайне языков. Знак указателя, синтаксически принадлежащий декларатору, но семантически являющийся частью типа, – не исключение. Ужас по типу int *a[10] и тем более void (*foo)(int) – туда же.

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

на таком if икнет любой кроме сишника(и плюсовика)

А если с англичанином на китайском заговорить, он тоже, вполне вероятно, «икнёт».

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

Конкретно в моём примере идентичное выражение было написано специально, поскольку именно разница (меньше, равно или больше нуля) нас интересует: её мы проверяем, её мы и возвращаем.

и числа к булам автоматически не приводятся в силу строгой типизации.

У вас не приводятся, у нас приводятся. Я библиотеку не на Аде пишу.

Впрочем, на Аде я бы в данном случае написал скорее всего тоже:

Не x /= y, а (x - y) /= 0.

wandrien ★★
()
Последнее исправление: wandrien (всего исправлений: 1)
Ответ на: комментарий от wandrien

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

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

Конкретно в моём примере идентичное выражение было написано специально, поскольку именно разница (меньше, равно или больше нуля) нас интересует: её мы проверяем, её мы и возвращаем.

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

У вас не приводятся, у нас приводятся. Я не библиотеку не на Аде пишу.

Я вот стараюсь избегать неявного преобразования типов, т.к. оно много подводных камней таит. Но раньше – да, любил, т.к. это делает код более кратким,

if (!ptr) return do_panic();

но теперь предпочитаю писать

if (ptr == NULL) return do_panic();

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

имхо, по логике там все же проверка сравнения срок через совпадение длин

Не только. Эта функция не просто отвечает на вопрос о равенстве, но должна задавать однозначное упорядочивание элементов.

Пофиг, какое оно конкретно, главное, чтобы для любых a, b, c выполнялось условие:

Если a < b и b < c, то a < c.

wandrien ★★
()
Ответ на: комментарий от wandrien
  1. Дублирование кода может служить во благо, если есть подозрение, что дубли не обязаны вести себя одинаково. В таком случае независимый дубль править при необходимости психологически проще и понятно, что это допустимо. Ну и по невнимательности не сломаешь другие зависимости.
    У этого, разумеется, есть и очевидный минус любых дублей: исправления и оптимизации нужно вносить в нескольких местах и можно что-то забыть.

  2. Я тут согласен с остальными: не понятно зачем делать вычитание в условии. != в условии просто и понятно. Вычитание в условии не настолько очевидно. Было бы оно в отдельной переменной:

int64_t diff = s1->value_size - s2->value_size;
if (diff) return diff;

код был бы очевиднее, но учитывая, что этот diff нигде больше не используется простое сравнение value_size’ов в if() и возврат вычитания на мой взгляд предпочтительнее.

if (s1->value_size != s2->value_size) {
    return s1->value_size - s2->value_size;
}
  1. В данном случае на плюсах я тоже предпочёл бы сравнение в if(), даже учитывая что объявление переменной можно в сам if запихать.
Ivan_qrt ★★★★★
()
Ответ на: комментарий от wandrien

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

А так то можно было бы написать в стиле firkax:

if( (s = a - b) && (return s)) // BAD STYLE

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

Не только. Эта функция не просто отвечает на вопрос о равенстве, но должна задавать однозначное упорядочивание элементов.

Функция да. Но конкретно этот return должен вызываться только в том случае, когда длины не совпадают. А если они совпадают, то не вызываться. При этом что именно возвращается из функции написано в самом return, а не в условии его вызова.

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

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

- Если длины не равны.

- Если разница длин не равна нулю.

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

Вот если кто-то мне еще расскажет какие-то открытия по bool в Си, другое дело))

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

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

    const int size_diff = first->value_size - second->value_size;
    if (size_diff != 0) return size_diff;
soomrack ★★★★★
()
Ответ на: комментарий от cudeta

Я не знаю, как там что устроено в контейнерах из glib2, но есть подозрение, что с проверкой на NULL тут есть какая-то проблема с логикой.

Проблема с логикой возникнет в случае, если NULL будет сравниваться как не равный NULL. Кстати, в какую сторону вы будете его тогда делать не равным? NULL < NULL или NULL > NULL? И как отличить их между собой?

В этом случае будет нарушено фундаментальное условие для упорядочивания элементов:

Если a < b и b < c, то a < c.

В лучшем случае ваша программа зависнет или вылетит.

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

wandrien ★★
()
Последнее исправление: wandrien (всего исправлений: 2)
Ответ на: комментарий от wandrien

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

Вы говорите про какие-то «элементы», но вы не уточнили, что представляет из себя «элемент» и какой контейнер используется. Если элемент — это структура, то она не может быть NULL (и не может располагаться по адресу NULL). А если это указатель, то (1) непонятно, зачем лишее косвенное обращение; (2) ваша функция должна принимать FmSymbol **, а не FmSymbol *, если мы говорим про glib и GCompareFunc.

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

cudeta
()
Последнее исправление: cudeta (всего исправлений: 1)
Ответ на: комментарий от cudeta

Это класс для константных ref-counted «строк» (фактически - любых байтовых последовательностей), пригодных для использования в контейнерах.

На своём уровне абстракции тут нет проблемы в логике. А проблема в логике клиентского кода, если таковая есть, выходит за пределы компетенции данного класса.

А если это указатель, то (1) непонятно, зачем лишее косвенное обращение

Почему вы решили, что здесь есть лишнее косвенное обращение?

(2) ваша функция должна принимать FmSymbol **, а не FmSymbol *, если мы говорим про glib и GCompareFunc.

Непонятно, о чем речь. Контейнеры glib работают с «объектами», которые доступные к интерпретации как указатели на произвольные данные или как целые размера intptr_t (просто в силу своего битового размера). Сам контейнер никак не интерпретирует значение и не пытается его разъименовать или иным образом использовать, он просто его хранит.

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

Почему вы решили, что здесь есть лишнее косвенное обращение?

Потому что glib хранит указатели на данные (key или value) там, где нужно хранить сами данные. Это и есть лишнее косвенное обращение (indirection) и, на практике, лишние выделения динамической памяти.

Непонятно, о чем речь. Контейнеры glib работают с «объектами», которые доступные к интерпретации как указатели на произвольные данные или как целые размера intptr_t (просто в силу своего битового размера). Сам контейнер никак не интерпретирует значение и не пытается его разъименовать или иным образом использовать, он просто его хранит.

А зачем, на практике, там хранить NULL?

cudeta
()