LINUX.ORG.RU

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

 


13

7

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

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

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

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

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

★★★★★

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

Проверил на коротких строках — работает правильно. Длинные, сам понимаешь, ломает просматривать.

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

А если мне *нужно* склеить 2 текста? Может, я в дальнейшем планирую обрабатывать их как единое целое. Например, отобразить оба в виджете Tkinter и прочитать вывод.

Есть еще и такая задача, например, сделать перевод/автозамену. Загружаю словарь с оригиналом и переводом в >30k строк и пропускаю изначальный фрагмент через replace. Мои тесты показывают, что единый текст обрабатывается быстрее, чем много фрагментов. Или тут ошибка какая-то?

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

Без косяков. (ну, на мой взгляд по крайней мере)

gcc -Wall -Werror -Wextra -O1 2.c -o teststrconcat 
14.12.24 20:47 /tmp
time for i in $(seq 1 10); do ./teststrconcat > /dev/null; done

real	0m8.311s
user	0m8.270s
sys	0m0.012s
14.12.24 20:47 /tmp
gcc -Wall -Werror -Wextra -O2 2.c -o teststrconcat 
14.12.24 20:47 /tmp
time for i in $(seq 1 10); do ./teststrconcat > /dev/null; done

real	0m4.387s
user	0m4.355s
sys	0m0.014s
14.12.24 20:47 /tmp
gcc -Wall -Werror -Wextra -O3 2.c -o teststrconcat 
14.12.24 20:51 /tmp
time for i in $(seq 1 10); do ./teststrconcat > /dev/null; done

real	0m4.386s
user	0m4.349s
sys	0m0.019s
Eddy_Em ☆☆☆☆☆
()

О, какой ТС флудообразователь-то!

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

Например, отобразить оба в виджете Tkinter

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

Загружаю словарь с оригиналом и переводом в >30k строк

А где тут собственно склейка? Загружать можно сразу одним куском.

no-such-file ★★★★★
()
Ответ на: комментарий от makoven

так было в предшественнике С.

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

когда же указатель: адрес содержащий адрес

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

ну посмотри главу про указатели массивы в кернигане_ричи или каком генерике :)

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

считай имя_массива синонимом адреса начального элемента

а имя переменой указателя адрес где содержится адрес

когда идёт копирование значения в аргументы

имя_массива это всегда адрес

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

qulinxao ★★☆
()
Ответ на: комментарий от Eddy_Em
#include <string>
#include <cstdio>
#include <ctime>
#include <cstdlib>

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;
}

int main(){
  try {
    srand(time(NULL));
    std::string s = genrandstr(rand() % 1024 + 10);

    for(int i = 0; i < 1000; ++i){
      s += genrandstr(rand() % 1014 + 10);
      printf("got: %s\n\n", s.c_str());
    }
    printf("%s\n", s.c_str());
    return 0;
  } catch (std::exception &e) {
    printf("%s\n", e.what());
    return -1;
  }
}
$ g++ test1.cpp -o test1cpp -O2 -Wall -Werror -Wextra --std=c++11
$ time for i in $(seq 1 10); do ./test1cpp > /dev/null; done

real    0m2.207s
user    0m2.184s
sys     0m0.024s
$ time for i in $(seq 1 10); do ./test1c > /dev/null; done

real    0m2.326s
user    0m2.306s
sys     0m0.022s

test1c - твой.

invy ★★★★★
()
Последнее исправление: invy (всего исправлений: 2)
Ответ на: комментарий от no-such-file

И зачем тебе их нужно было изначально хранить отдельно?

Потому что IRL удобно иметь у себя в $HOME несколько документов, а не хранить все в одном 5000-страничном файле.

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

В Tkinter и так манипулировать текстом - сплошной гемор, а манипулировать несколькими фрагментами - гемор, помноженный на число фрагментов.

А где тут собственно склейка? Загружать можно сразу одним куском.

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

Deleted
()
Ответ на: комментарий от no-such-file

Т.е. по твоему, работа со списками, а также циклом for в питоне будет быстрее и менее затратной по памяти, чем конкатенация строк? Ну-ну.

Кроме того, питон нужен, чтобы писать нормально читаемый код. Если оборудование такое плохое, что не хватает памяти, чтобы сложить 2 небольших текстовых файла... Ну тогда я не знаю. Используй ассемблер, или прикупи памяти. Понятно, что если текстовики весят по несколько гигабайт, то и алгоритмы имеет смысл немного подправить.

Deleted
()
main() {
    char * a = "hello ";
    char * b = "world";
    char c[255];
    char * d = c;
    for (; *(d++)=*(a++); a);
    d--;
    for (; *(d++)=*(b++); b);
    printf("%s\n", c);
}
PolarFox ★★★★★
()
Ответ на: комментарий от PolarFox

Или лучше написать вот так:

main() {
    char * a = "hello ";
    char * b = "world";
    char c[255];
    char * d = c;
    while (*(d++)=*(a++));
    d--;
    while (*(d++)=*(b++));
    printf("%s\n", c);
}
PolarFox ★★★★★
()

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

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

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

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

примитивным подходом

тут проблема в том, КУДА выводить. Твоя функция выводит всегда в /dev/stdout. Это не есть хорошо.

Ну или если прямо сильно нужно единой строкой

тут проблема в том, что malloc закопан где-то в функции, а free снаружи.

Ну и все эти примеры слишком многословны, мне больше нравится C++, где просто str = f() + st.f() + «XYZ» + 123; или типа того. Так оно намного нагляднее. А printf хороша только в мелких программках.

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

Сделать char buf[n+m+1] где n - длина строки a, а m - длина строки b - не вариант?

в C это не вариант. Нужно обязательно malloc/free(или свой велосипед, если стандартные malloc(3)/free(3) не нравятся).

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

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

А закодить один раз это в процедуру никак-никак нельзя?

можно. Но тогда вместо «строки» char* необходимо использовать свою структуру. Иначе на 100й строке у твоей программы начнётся течка. Char* это просто тупо адрес, тут слишком мало информации. Даже непонятно, ОТКУДА этот адрес? В сишке адрес может быть

1. строковый литерал, типа «hello world!»

2. статический буфер (не обязательно глобальный)

3. буфер на стеке

4. результат malloc

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

Или пользоваться готовыми

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

emulek
()
Ответ на: комментарий от post-factum

Ты про то, что в стеке создается и может тебе фантастические приколы обеспечить?

C99 не нужен! А уж тем паче с11!!!

Eddy_Em ☆☆☆☆☆
()

Вы тут 8 страниц строки склеиваете? Жесть.

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

Собственно, я и использовал nodejs до тех пор пока не понадобилось тесное сотрудничество с несколькими сишныси либами. v8 ffi показался мне настолько ужасающим, что было решено писать на чистом си, приправляя lua в местах, которые будут менятся в процессе эксплуатации поделия)

Перл пхп питон и прочий GIL - поделки для младшекласников

makoven ★★★★★
() автор топика
Последнее исправление: makoven (всего исправлений: 1)
Ответ на: нубовопрос от wakuwaku

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

это не нужно ИМХО.

calloc обычно сподручней.

calloc это плохая практика ИМХО, т.к. вынуждает использовать ВСЮ выделенную память как резидентную, даже если она не нужна.

Ну и время тратится лишнее.

ИМХО это только для отладки приемлемо, и то не всегда.

emulek
()
Ответ на: комментарий от post-factum

Хорошо, что это только твоё ХО.

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

Это во первых. Во вторых: это не только и не столько моё мнение. Если-бы разрабы какого-нить ФФ юзали-бы calloc, то посмотри, сколько-бы у тебя ФФ жрал-бы физической памяти(в столбце VIRT). У меня столько памяти тупо нет.

emulek
()
Ответ на: нубовопрос от wakuwaku

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

это не нужно ИМХО.

calloc обычно сподручней.

calloc это плохая практика ИМХО, т.к. вынуждает использовать ВСЮ выделенную память как резидентную, даже если она не нужна.

Ну и время тратится лишнее.

ИМХО это только для отладки приемлемо, и то не всегда.

emulek
()

Не слушай никого, С без ассемблера для черепашек, как то так надо

asm{
        PUSH    EDI
        PUSH    ESI
        MOV     ESI,EAX
        MOV     EDI,EDX
        MOV     ECX,0FFFFFFFFH
        XOR     AL,AL
        REPNE   SCASB
        NOT     ECX
        MOV     EDI,ESI
        MOV     ESI,EDX
        MOV     EDX,ECX
        MOV     EAX,EDI
        SHR     ECX,2
        REP     MOVSD
        MOV     ECX,EDX
        AND     ECX,3
        REP     MOVSB
        POP     ESI
        POP     EDI
}

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

Держи мой велосипед лучше. При dest равном 0 работает аналогично strdup, при src равном 0 просто no-op.

char *strbuf(char *dest, const char *src)
{
    size_t len_dest = dest ? strlen(dest) : 0;
    size_t len_src = src ? strlen(src) + 1 : 0;
    dest = realloc(dest, len_dest + len_src);
    if (dest) memcpy(dest + len_dest, src, len_src);
    return dest;
}

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

Ну, главное — начать.

В конце-концов совместным трудом нарожаем что-нибудь дельное.

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

А где велосипед? Давай уж, тоже опозорься каким-нибудь страшным кодом.

Я не Xellos, но это для всех обязательно?

Тогда нате мой(надеюсь, что хоть немного оригинален):

char *strct_impl(const char *s1, size_t sz1, const char *s2, size_t sz2)
{
    char *buf = malloc(sz1 + sz2 + 1);
    memcpy(buf, s1, sz1);
    memcpy(buf + sz1, s2, sz2 + 1);
    return buf;
}
 
#define strsz(s) _Generic((&s), \
 char **: strlen(s), \
 const char **: strlen(s), \
 default: sizeof(s) - 1)
 
#define strct(s1, s2) \
 strct_impl(s1, strsz(s1), s2, strsz(s2))

Можно еще сделать вариант с размещением на стеке, но лень.

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