LINUX.ORG.RU

Шок от С. Как склеивать строки?

 


13

7

Осваиваю си. Всё шло хорошо пока внезапно не понадобилось склеить строки (константные и переменные). Покурил stackoverflow. Предлагают 2 варианта:

Первый - создать char buf[молись_чтобы_хватило] и делать str(n)cat/sprintf в этот buf.

Второй - использовать asprintf, который расширение, нестандарт и вообще.

Вопрос: как вы склеиваете строки? Может есть какая-нибудь общепринятая либа?

Простите за нубский вопрос

★★★★★

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

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

А вот этот мужчина очень сильно ругается на исключения. И на невозможность их отключить

Их можно отключить практически в любом крестовом компиляторе. А мужчина - практически классический пример неосилятора С++.

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

Это извращение — обзывать конкатенацию двух больших кусков памяти знаком «+»

Так сделано в очень многих языках. Это удобно.

В С все логично делается

То-то ты не справился :)

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

Он таки осилил. Его поделие даже в репах есть. К RAII у него, кстати, тоже есть претензия. Если екзепшн вылетает в деструкторе - то поймать его не удастся и крэшится весь процесс.

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

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

Не так, если мы бросаем исключение, когда уже происходит раскрутка стека к try/catch, т.е. в момент обработки другого исключения - вызывается terminate() (можно кстати задать свой обработчик). Потому как возникает дилемма - забить на новое исключение или на старое. А просто бросок исключения из деструктора - прекрасно споймается.

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

ну как минимум памяти уйдет приблизительно в два раза больше, ~2 ГБ

для таких случаев лучше все же realloc

mazdai ★★★
()
Ответ на: комментарий от mazdai
$ time for i in $(seq 1 10); do ./test2c > /dev/null; done

real    0m4.073s
user    0m3.200s
sys     0m0.876s
$ time for i in $(seq 1 10); do ./test1cpp > /dev/null; done

real    0m2.267s
user    0m2.247s
sys     0m0.023s

test2c - Шок от С. Как склеивать строки? (комментарий)
test1cpp - Шок от С. Как склеивать строки? (комментарий)

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

Еще надо учесть символ конца строки

size_t sumlen = strlen(a)+strlen(b)+1;
char *concat = malloc(sumlen);
snprintf(concat, sumlen, "%s%s", a, b);

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

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

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

Не стоит позволять исключениями покидать деструктор. Это плохая идея. И глупая.

Согласен, я просто расписал, что и как происходит на самом деле.

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

Читать темы с конца надо начинать! Вон, сообщения для чего по-твоему сортируются по порядку "новые вверху"?

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

Это извращение — обзывать конкатенацию двух больших кусков памяти знаком «+». Ничего общего со сложением эта операция не имеет.

Это не извращение, ты оперируешь объектом, который имеет свойства и методы. «+» - условно для удобства назвали метод объекта для контактации. Вот в математике, существует умножение чисел, согласно твоей логике, векторное умножение - это «извращение аналитической геометрии». В ООП, в случае строк, мы асбрагируемся (инкапсулируемся) от памяти, все это спрятано внутри объекта и нам необязательно знать, как оно работает внутри, логично?

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

ясно. ты меня с другим анонимусом попутал

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

символ + в качестве конкатенации это кто то поленился да и набор символов небогат

но даже ^ могло бы лучше ибо нет ассоциаций вбитых школьным образованием к символу +

тоже и с присвоением.

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

Не так, если мы бросаем исключение, когда уже происходит раскрутка стека к try/catch, т.е. в момент обработки другого исключения - вызывается terminate() (можно кстати задать свой обработчик). Потому как возникает дилемма - забить на новое исключение или на старое.

А зачем забивать? Почему не кинуть исключение, включающее в себя информацию об обоих (и всех последующих) исключениях? Никогда не понимал этого запрета. Ну кинул деструктор исключение и фиг с ним, сохранили и дальше раскручиваемся.

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

символ + в качестве конкатенации это кто то поленился да и набор символов небогат

Если честно, то вообще абсолютно все равно, можешь хоть HERWENDASOSI назови. Мне это никогда не мешалоло.

но даже ^ могло бы лучше ибо нет ассоциаций вбитых школьным образованием к символу +

Когда я біл в десятом классе, я одному парню писал класс на С++ реализющий векторі в геометрическом смісле для какой-то лабі. Оператор ^ біл векторным умножением, т.к. был наиболее похож на символ перпендикулярности и позволял векторное умножение записать как a ^ b * c просто перегрузив операторі ^ и *.

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

озволял векторное умножение записать

точне смешанное векторное умножение

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

Это как kernel v.0.1 скомпилить и пытаться запустить на современном железе, матерясь, что нихрена не робит.

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

Почему не кинуть исключение, включающее в себя информацию об обоих (и всех последующих) исключениях?

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

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

тем что new может бросить ещё одно исключение?

когда разберёшься с исключениями и поймёшь, что «информация», которую они носят — это ерунда, тогда перестанешь предлагать такие глупости.

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

Почему не кинуть исключение, включающее в себя информацию об обоих (и всех последующих) исключениях

Зачем? Что тебе даст эта информация? Что ты сможешь сделать, зная её?

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

УМВР

Вспомнился анекдот:

Если бы программисты были врачами, им бы пациенты говорили например «у меня болит нога», а они бы отвечали «ну не знаю, у меня такая же нога, а ничего не болит».

EvilFox ★★
()

Осваиваю си. Всё шло хорошо пока внезапно не понадобилось склеить строки (константные и переменные). Покурил stackoverflow.

Сдаётся мне, тебе надо всё же сначала K&R покурить. К strcat там явная отсылка.

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

Нет. Это Си. Например, первый попавшийся кусок из линуксового ядра:

        char name[MAX_CGROUP_TYPE_NAMELEN + MAX_CFTYPE_NAME + 2] = { 0 };
        if (subsys && !test_bit(ROOT_NOPREFIX, &cgrp->root->flags)) {
                strcpy(name, subsys->name);
                strcat(name, ".");
        }
        strcat(name, cft->name);

(это файл kernel/cgroup.c, если что)

Си - это высокоуровневый ассемблер, и наркоманский синтаксис работы со строками - это плата за эффективность. Когда ты пишешь плюсик в C++ или паскале, тебе генерируется куча кода, а в Си ты этот код сам контролируешь.

Да, ты можешь использовать GLib, как тут советовали, но по сути, это псевдо-ООП. И тут встаёт вопрос, а что лучше - извращаться в процедурном языке с костылями или всё-таки смотреть в сторону C++ и STL? Это на самом деле серьёзный вопрос, и однозначного ответа на него нет. Собственно, срач GTK vs Qt растёт именно отсюда.

Лично я считаю, что когда пишешь _прикладные_ программы - лучше C++, чем его имитация. А если хочешь писать на настоящем Си (то же линуксовое ядро) - то именно strcat.

Я бы тебе посоветовал пописать на Си без костылей (с malloc, strcat и др.), а потом, не отвлекаясь на костыли - на C++ и STL. В любом случае, после того, как освоишь и то и другое, ты сможешь квалифицированно выбирать инструмент под задачу.

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

На самом деле использовать C++ легче, начинать с него легче.

Использовать - да (по крайней мере, для десктопного прикладного софта), а начать всё же лучше с Си. Чтобы потом понимать, откуда что растёт.

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

тем что new может бросить ещё одно исключение?

Не может. Если бросает, тут можно и терминироваться. Но по факту не бросит.

когда разберёшься с исключениями и поймёшь, что «информация», которую они носят — это ерунда, тогда перестанешь предлагать такие глупости.

С исключениями я разобрался лет 10 назад. Эта информация очень важна и помогает фиксить баги.

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

Зачем?

Чтобы не вводить глупые ограничения на запрет исключений в деструкторах. Например в Java такого ограничения нет и всё прекрасно работает.

Что тебе даст эта информация? Что ты сможешь сделать, зная её?

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

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

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

Икспердов трэд.

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

Например в Java такого ограничения нет и всё прекрасно работает.

Ты не знаешь Java, если там смоделировать похожую ситуацию через try-with-resources, то второе исключение тупо не обработается. В С++ могли сделать так же, но разумно решили, что лучше «упасть», чем забить на исключение. Кстати в С++11 все деструкторы по-умолчанию noexcept и любое исключение из них будет сразу же вызывать terminate, что тоже логично и приучит погроммистов ставить try/catch в своих деструкторах.

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

Ты не знаешь Java, если там смоделировать похожую ситуацию через try-with-resources, то второе исключение тупо не обработается.

Это ты не знаешь Java. Второе исключение добавится в список suppressed первого. Любой желающий легко его оттуда достанет.

В С++ могли сделать так же, но разумно решили, что лучше «упасть», чем забить на исключение.

А ещё лучше не забивать и не падать.

Кстати в С++11 все деструкторы по-умолчанию noexcept и любое исключение из них будет сразу же вызывать terminate, что тоже логично и приучит погроммистов ставить try/catch в своих деструкторах.

Это приучит их только к тупому подавлению всех исключений. Что, как ты сказал, даже хуже, чем падение.

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

Мне интересно, а вообще в C++ как передать информацию об исключительной ошибки из деструктора? Через глобальный errno? Вот я файл закрываю, а мне OS сказала мол device write error. Надо бы кинуть исключение, чтобы сверху оно поймалось и дало пользователю знать об этом. Какой каноничный способ тут должен применяться?

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

Это ты не знаешь Java. Второе исключение добавится в список suppressed первого. Любой желающий легко его оттуда достанет.

Ты думаешь я этого не знал? Вот только «любой желающий легко его оттуда достанет» - это и есть «забить на исключение». Почему, должен сам догадаться.

А ещё лучше не забивать и не падать.

Как видишь в С++ и Java не смогли эффективно и правильно разрулить эту ситуацию. Знаешь «правильные» аналоги с той же производительностью?

Это приучит их только к тупому подавлению всех исключений.

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

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

Вот я файл закрываю, а мне OS сказала мол device write error. Надо бы кинуть исключение, чтобы сверху оно поймалось и дало пользователю знать об этом.

event loop - и все в курсе, а именно на момент отработки деструктора оно уже никому не надо - объекта нет

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

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

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

Эти клоуны. Т.е. ты на полном серьёзе постишь эту клоунаду? Зачем ты лезешь в эту тему, что-то кому-то доказываешь/показываешь, пишишь какие-то бенчи, при этом нихрена в теме не понимая? Ты хоть понимаешь что ты намерил?

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

Вот ответь мне на пару вопросов:

а) Зачем ты впихнул сюда rand() и прочее убожество?:

std::string genrandstr(const int L) {
  std::string ret(L, 0);
  for(int i = 0; i < L; i++)
    ret[i] = 32 + (rand() % 95);
  return ret;
}

О чем ты думал, когда это писал? Что тобою двигало? Ты же делал это осмысленно, ну дак объясни мне - зачем?

б) Зачем ты впихнул сюда rand()?

s += genrandstr(rand() % 1014 + 10);

Объясни мне логику сего действа.

в) Зачем ты впихнул сюда printf(), да и вообще любой вывод?

      printf("got: %s\n\n", s.c_str());
    }
    printf("%s\n", s.c_str());

Что тобою двигало, в чем логика сего действа?

Я тебе не буду рассказывать про то, что конкатенация 5-ти метров строки у тебя исполняется 2+секунды. Пути нулей просто не исповедимы.

Ладно, предположим это тебе ниочем не говорит, но какбэ ((1024/2)^2)*1000 вывода на 1024/2 * 1000 байтов полезной работы - это ты хоть сможешь объяснить?

Ладно, давай что-то понаглядней, раз ты нихрена не представляешь, даже порядки величин:

#define _GNU_SOURCE
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

char * _strcat(char * dest, char * src) {
  uint64_t src_len = strlen(src) + 1, dest_len = strlen(dest);
  void * rs = realloc(dest, dest_len + src_len);
  memcpy(rs + dest_len, src, src_len);
  return rs;
}

char * genrandstr(uint64_t len)  {
  char * ret = malloc(len + 1);
  for(uint64_t i = 0; i < len; i++)
    ret[i] = 32 + (rand() % 95);
  ret[len] = 0;
  return ret;
}

int main(){
  srand(time(NULL));
  char * s = genrandstr(rand() % 1014 + 10);
  for(uint64_t i = 0; i < 1000; ++i){
    char * randstr = genrandstr(rand() % 1014 + 10);
    s = _strcat(s, randstr);
    free(randstr);
    write(STDOUT_FILENO, "got: ", sizeof("got: ") - 1);
    write(STDOUT_FILENO, s, strlen(s));
    write(STDOUT_FILENO, "\n\n", sizeof("\n\n") - 1);
  }
  write(STDOUT_FILENO, s, strlen(s));
  return 0;
}
$ time for i in $(seq 1 10); do ./testc > /dev/null; done

real    0m0.144s
user    0m0.116s
sys     0m0.008s

Давай дальше, я уже выше писал про проблему strlen():

#include <x86intrin.h>
uint64_t _nfnz(void * b) {
  __m128i null_mask = _mm_set1_epi8('\0'), v = _mm_cmpeq_epi8(null_mask, _mm_loadu_si128(b));
  return __builtin_ctzl(_mm_movemask_epi8(v));
}

uint64_t _ultra_fast_superhack_kill_strlen(void * str) {
  uint64_t is_mmaped_flag = 0x2;
  uint64_t size_flag = *((uint64_t *)str - 1), size = size_flag & ~0x7lu;
  if(size_flag & is_mmaped_flag) {
    size -= (4096 + sizeof(size_t) * 2);
    return size + (rawmemchr(str + size, 0) - (str + size));
  }
  size -= (sizeof(size_t) * 3);
  return size + _nfnz(str + size);
}

size_t strlen (const char *__s) {
  return _ultra_fast_superhack_kill_strlen(__s);
}
$ time for i in $(seq 1 10); do ./testc > /dev/null; done

real    0m0.042s
user    0m0.032s
sys     0m0.008s

Идём дальше:

char * genrandstr(uint64_t len)  {
  char * ret = memset(malloc(len + 1), 'a', len); *(ret + len) = 0;
  return ret;
}
$ time for i in $(seq 1 10); do ./testc > /dev/null; done

real    0m0.009s
user    0m0.000s
sys     0m0.000s
  for(uint64_t i = 0; i < 1000; ++i){
    char * randstr = genrandstr(rand() % 1014 + 10);
    s = _strcat(s, randstr);
    free(randstr);
  }
$ time for i in $(seq 1 10); do ./testc; done

real    0m0.004s
user    0m0.000s
sys     0m0.000s

Т.е. уже не измеряемо, в данном случае.

А теперь подумай, если конечно есть возможность, что же ты там намалякал и что намерил?

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

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

Вам в сторону D смотреть нужно.

string result = s1 ~ s2;

и никакого геморроя.

Xroft ★★
()
Последнее исправление: Xroft (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.