LINUX.ORG.RU

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

 


13

7

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

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

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

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

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

★★★★★

Последнее исправление: makoven (всего исправлений: 1)
char *concat(const char *s1, const char *s2) {
  int s1_len = strlen(s1);
  int s2_len = strlen(s2);
  char *result = malloc(s1 + s2 + 1);
  memcpy(result, s1, s1_len);
  memcpy(result + s1_len, s2, s2_len);
  result[s1_len + s2_len] = 0;
  return result;
}

SO читать не советую. Читай Керниган&Ритчи, читай классику про юникс, читай паттерны от GoF. C простой язык, с ним не должно быть проблем, которые сложно решить самому.

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

C простой язык, с ним не должно быть проблем, которые сложно решить самому.

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

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

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

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

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

Не вижу ничего плохого, чтобы инкапсулироваться от байтиков и всяких \0. Гораздо проще оперировать объектом «строкой», перегрузить оператор «+» и жить счастливой жизнью, чем копировать и перекладывать байтики. Тем более, ресурсы все равно не имеет смысла экономить на спичках, когда тут лежит под рукой ведроид с двумя гигабайтами оперативы, на котором жавамонср вертится потребляющий их все.

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

Не вижу ничего плохого, чтобы инкапсулироваться от байтиков и всяких \0.

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

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

лет 20 взад (возьмём эту точку отсчёта — я где-то тогда программировать начал) было так: есть posix, в котором описано как надо писать программы, чтобы они работали на зоопарке актуальных в те времена юниксов; при этом в нём (раньше — больше, сейчас — меньше) многие тонкости были оставлены на откуп реализациям. соответственно в манах можно было посмотреть что решили авторы твоей реализации libc (хотя завязывание на эти тонкости не приветствовалось даже тогда).

теперь причём здесь c**. в с89 стандартизированная часть библиотеки была довольно убогой (немаловажный фактор, поспособствовавший взлёту posix). в c99 утащили практически весь соответствующий раздел posix и с тех пор описание в стандарте с стало более приоритетным. о чём в posix часто упоминается под тегом cx (например, см цитату парой комментариев выше).

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

posix публикуется в вебе (opengroup.org) бесплатно то есть даром

anonymous
()
Ответ на: комментарий от Legioner
 memcpy(result, s1, s1_len);
  memcpy(result + s1_len, s2, s2_len);
  result[s1_len + s2_len] = 0;

копируй вторую строку сразу с нулем

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

Я так понимаю, c99/c11 реализованы в соответствующей операционной системе библиотеке libc. Не знаешь, данный момент linux *BSD и макось поддерживают C11? И почему многие не забьют на посикс и не начнут писать на C11?

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

Какие франки? Прямым тестом было написано - смотри pdf последнего черновика перед принятием:

C11: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf

C99: http://www.open-std.org/jtc1/sc22/WG14/www/docs/n1256.pdf

POSIX: http://pubs.opengroup.org/onlinepubs/9699919799/ http://pubs.opengroup.org/onlinepubs/009695399/

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

Да я нашел. Просто iso сайт улыбнул

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

соответствующей операционной системе библиотеке libc.

реализованы в соответствующем компиляторе + libc

Статус для gcc: https://gcc.gnu.org/wiki/C11Status

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

С11 или C99 это стандарты языка на котором корпорасты не пишут нового кода (кроме эмбеда).

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

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

я знаю. Но ты подумай: ты сначала смотришь память, и ищешь '\0' по одному байту, потом копируешь ту же память. Основное время будет тратится на штрафные такты из-за памяти.

лучше сразу читать, проверять, и писать.

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

Погуглил не знал что в микросхемы засовывают posix-совместимые операционки. Это ж какое поле для творчества. Неужто и правда можно писать фирмваре для микросхем на нормальном Си с нормальными функциями без детских ардуин и адского ассемблера

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

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

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

char * strapp(const char *first, const char *second) {
	size_t bytesCount = sizeof(char) * (strlen(first) + strlen(second));
	char *dest;

	/* returns NULL if malloc() fails */
	if(dest = malloc(bytesCount)) {
		memset(dest, 0, bytesCount);
		strcat(strcat(dest, first), second);
	}

	return dest;
}

int main(void) {
	const char *test = "Hello ";
	const char *test2 = "World!\n";
	char *dest = strapp(test, test2);

	if(!dest) { fprintf(stderr, "ERROR: Memory exhausted\n"); }
	else { fprintf(stdout, "%s\n", dest); }

	free(dest);
	return 0;
}
mazdai ★★★
()
Ответ на: комментарий от mazdai

Мне уже хватит функций для склеивания

makoven ★★★★★
() автор топика
Ответ на: комментарий от mazdai
	if(dest = malloc(bytesCount)) {
		memset(dest, 0, bytesCount);
		strcat(strcat(dest, first), second);
	}

LOR - сборище дебилов сообщество альтернативно одаренных программистов.

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

обычно используют FreeRTOS + newlibc + LwIP

т.е. и не ассемблер, но и не posix.

ближе к arduino - chibios и mbed

из почти posix: http://www.nuttx.org/

а так - да, можно с usart работать через open/fopen *read *write, fprintf кучей и прочим.

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

Блин, даже самому стало интересно чего там. Там же REPNE SCASB, это же над строкой операция. Строковых копирование с проверкой вроде нет инструкций. А циклы медленнее,по крайней мере раньше, считались, там же прыгать приходилось. В итоге две строковые быстрее цикла.

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

SCAS/MOVS уже тыщу лет никто не юзает. Нет смысла, процессоры очень быстрые, а вот DRAM в основном только объём увеличивает(по сравнению с CPU). Потому твой код будет работать медленнее, чем классика жанра

while(*dest++ = *src++);
Всё равно процессор будет стоять и ждать, пока байты из памяти(кеша) прочитаются.

Сейчас тормозит в порядке уменьшения:

1. I/O

2. память (и промахи в разные кеши)

2.1 неконсистентность памяти. Память у нас одна, а ядер много. Если мы решили задействовать пару ядер, то одно ядро будет тормозить другое из-за того, что данные должны быть одинаковыми с т.з. каждого ядра. Т.е. если мы записали 17 в ячейку памяти, а второе ядро читает это 17(или рядом), то 17 должно сначала пройти по кешам первого ядра в память, а потом по кешам из второго ядра в ALU. При этом ползти будет не 8 бит, а как минимум 256.

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

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

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

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

Я тебе говорю, что от циклов надо избавляться, ибо это тоже переход, ты мне рассказываешь, что надо избавляться от переходов. Ты не находишь это странным? Переходы не всегда конвейер портят,кстати, только если нужной команды в очереди нет. И чем MOVS, выполняемое как единое целое будет, тормозить другие ядра больше, чем куча последовательных обращений. Один раз подождать или 1000 раз подождать. Гонишь ты чего-то. зы. Это ты, кстати, код из strcopy критикуешь, так что поосторожнее.

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

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

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

Я тебе говорю, что от циклов надо избавляться, ибо это тоже переход, ты мне рассказываешь, что надо избавляться от переходов. Ты не находишь это странным?

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

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

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

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

у тебя там ещё и SCAS.

Это ты, кстати, код из strcopy критикуешь, так что поосторожнее.

я в курсе. Для i8086, только 32х битная версия.

А вот тебе другая версия:

/* Copy SRC to DEST.  */
char *
strcpy (char *dest, const char *src)
{
  char c;
  char *s = (char *) src;
  const ptrdiff_t off = dest - s - 1;

  do
    {
      c = *s++;
      s[off] = c;
    }
  while (c != '\0');

  return dest;
}
из glibc-2.20

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

из glibc-2.20

это версия на «от%бись», для популярных платформ (x86, x86_64 в т.ч.) есть отдельные версии

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

C простой язык, с ним не должно быть проблем, которые сложно решить самому.

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

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

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

это переносимая версия, которая сносно работает на всех поддерживаемых платформах. компиляторы часто таскают (и партизански подставляют, что иногда расстраивает) свои.

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

это переносимая версия, которая сносно работает на всех поддерживаемых платформах

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

anonymous
()

будь проще. c:=a+b паскаль наше вё, остальные языки не нужны

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

хорошо-хорошо, версия glibc-2.20/sysdeps/x86_64/strcpy.S

#include <sysdep.h>
#include "asm-syntax.h"

#ifndef USE_AS_STPCPY
# define STRCPY strcpy
#endif

	.text
ENTRY (STRCPY)
	movq %rsi, %rcx		/* Source register. */
	andl $7, %ecx		/* mask alignment bits */
	movq %rdi, %rdx		/* Duplicate destination pointer.  */

	jz 5f			/* aligned => start loop */

	neg %ecx		/* We need to align to 8 bytes.  */
	addl $8,%ecx
	/* Search the first bytes directly.  */
0:
	movb	(%rsi), %al	/* Fetch a byte */
	testb	%al, %al	/* Is it NUL? */
	movb	%al, (%rdx)	/* Store it */
	jz	4f		/* If it was NUL, done! */
	incq	%rsi
	incq	%rdx
	decl	%ecx
	jnz	0b

5:
	movq $0xfefefefefefefeff,%r8

	/* Now the sources is aligned.  Unfortunatly we cannot force
	   to have both source and destination aligned, so ignore the
	   alignment of the destination.  */
	.p2align 4
1:
	/* 1st unroll.  */
	movq	(%rsi), %rax	/* Read double word (8 bytes).  */
	addq	$8, %rsi	/* Adjust pointer for next word.  */
	movq	%rax, %r9	/* Save a copy for NUL finding.  */
	addq	%r8, %r9	/* add the magic value to the word.  We get
				   carry bits reported for each byte which
				   is *not* 0 */
	jnc	3f		/* highest byte is NUL => return pointer */
	xorq	%rax, %r9	/* (word+magic)^word */
	orq	%r8, %r9	/* set all non-carry bits */
	incq	%r9		/* add 1: if one carry bit was *not* set
				   the addition will not result in 0.  */

	jnz	3f		/* found NUL => return pointer */

	movq	%rax, (%rdx)	/* Write value to destination.  */
	addq	$8, %rdx	/* Adjust pointer.  */

	/* 2nd unroll.  */
	movq	(%rsi), %rax	/* Read double word (8 bytes).  */
	addq	$8, %rsi	/* Adjust pointer for next word.  */
	movq	%rax, %r9	/* Save a copy for NUL finding.  */
	addq	%r8, %r9	/* add the magic value to the word.  We get
				   carry bits reported for each byte which
				   is *not* 0 */
	jnc	3f		/* highest byte is NUL => return pointer */
	xorq	%rax, %r9	/* (word+magic)^word */
	orq	%r8, %r9	/* set all non-carry bits */
	incq	%r9		/* add 1: if one carry bit was *not* set
				   the addition will not result in 0.  */

	jnz	3f		/* found NUL => return pointer */

	movq	%rax, (%rdx)	/* Write value to destination.  */
	addq	$8, %rdx	/* Adjust pointer.  */

	/* 3rd unroll.  */
	movq	(%rsi), %rax	/* Read double word (8 bytes).  */
	addq	$8, %rsi	/* Adjust pointer for next word.  */
	movq	%rax, %r9	/* Save a copy for NUL finding.  */
	addq	%r8, %r9	/* add the magic value to the word.  We get
				   carry bits reported for each byte which
				   is *not* 0 */
	jnc	3f		/* highest byte is NUL => return pointer */
	xorq	%rax, %r9	/* (word+magic)^word */
	orq	%r8, %r9	/* set all non-carry bits */
	incq	%r9		/* add 1: if one carry bit was *not* set
				   the addition will not result in 0.  */

	jnz	3f		/* found NUL => return pointer */

	movq	%rax, (%rdx)	/* Write value to destination.  */
	addq	$8, %rdx	/* Adjust pointer.  */

	/* 4th unroll.  */
	movq	(%rsi), %rax	/* Read double word (8 bytes).  */
	addq	$8, %rsi	/* Adjust pointer for next word.  */
	movq	%rax, %r9	/* Save a copy for NUL finding.  */
	addq	%r8, %r9	/* add the magic value to the word.  We get
				   carry bits reported for each byte which
				   is *not* 0 */
	jnc	3f		/* highest byte is NUL => return pointer */
	xorq	%rax, %r9	/* (word+magic)^word */
	orq	%r8, %r9	/* set all non-carry bits */
	incq	%r9		/* add 1: if one carry bit was *not* set
				   the addition will not result in 0.  */

	jnz	3f		/* found NUL => return pointer */

	movq	%rax, (%rdx)	/* Write value to destination.  */
	addq	$8, %rdx	/* Adjust pointer.  */
	jmp	1b		/* Next iteration.  */

	/* Do the last few bytes. %rax contains the value to write.
	   The loop is unrolled twice.  */
	.p2align 4
3:
	/* Note that stpcpy needs to return with the value of the NUL
	   byte.  */
	movb	%al, (%rdx)	/* 1st byte.  */
	testb	%al, %al	/* Is it NUL.  */
	jz	4f		/* yes, finish.  */
	incq	%rdx		/* Increment destination.  */
	movb	%ah, (%rdx)	/* 2nd byte.  */
	testb	%ah, %ah	/* Is it NUL?.  */
	jz	4f		/* yes, finish.  */
	incq	%rdx		/* Increment destination.  */
	shrq	$16, %rax	/* Shift...  */
	jmp	3b		/* and look at next two bytes in %rax.  */

4:
#ifdef USE_AS_STPCPY
	movq	%rdx, %rax	/* Destination is return value.  */
#else
	movq	%rdi, %rax	/* Source is return value.  */
#endif
	retq
END (STRCPY)

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

все же лососните, это будет работать так, как задумано и без утечек памяти. в условном операторе идет проверка (dest == 0 или &dest[0] == NULL), memset() не выполнится с нулевым указателем

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

Кстати, что ты имеешь в виду? То, что calloc() сразу инициирует выделенную память, что может привести к вылету приложения при недостатке физической оперативы? И чем же это плохо? Я считаю, что это классно!

Или ты имеешь в виду, что calloc != malloc+memset? Ну ты же сам коды приводил, где внутри calloc вызывался malloc, а затем memset, не так разве?

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

В твоем случае реально фигня написана! Ну зачем тебе делать memset, если ты все равно тут же заполнишь всю эту память? Лучше тупо сделай memcpy первой строки без завершающего нуля, а потом memcpy с завершающим.

if(dest = malloc(bytesCount)) {
		memset(dest, 0, bytesCount);
		strcat(strcat(dest, first), second);
	}
Ну, а еще выше упоминали, что strcat по сути дополнительно strlen вызывает, чтобы длину узнать. А ты уже strlen вызывал, чтобы узнать, сколько надо выделить.

А еще непонятно, нафига делать malloc вместо realloc. Хочется получить третью строку из первых двух что ли, не трогая оригиналы?

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

Я считаю, что это классно!

Я тоже, но говорил не об этом.

calloc != malloc+memset?

Да.

Ну ты же сам коды приводил, где внутри calloc вызывался malloc, а затем memset, не так разве?

Перестань циркачить, пожалуйста.

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

Открываем код по твоей ссылке [1] и видим: какой-то malloc на строке 3179 и memset на 3183 (по какому условию они выполняются, лень по коду искать); дальше в зависимости от условий выполняются другие виды выделения памяти, опять memset на строках 3236 и 3260. Все там читать лень, да и непонятные какие-то внутренние функции и макросы используются.

По ссылке же [2] пишут, что в отличие от memset, calloc меняет лишь несколько страниц памяти.

Хрень какая-то! И как может calloc обнулить лишь пару-тройку страниц из выделенной памяти, если он должен гарантировать, что вся память будет в нулях?

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

может привести к вылету приложения при недостатке физической оперативы? И чем же это плохо? Я считаю, что это классно!

что тут «классного», если обычно приложение 20..80% выделенной памяти не использует? Ну если это не жаба и не твой говнокод.

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

третий пункт - да

второй - что-то такое помню, но strcat мне strlen отдать не может, от только возвращает указатель на строку, в которую копирует

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

если не делать memset, то может выйти нечто вроде «G .Hello World!» вместо «Hello World!». по указателю уже есть символы и их нужно стереть.

почему волнуется постфактум, не понимаю. в чем проблема обращаться к строке как к массиву? (хоть это и извращение :-) ) <указатель на строку>[номер элемента], так тоже можно... что не так?

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

если обычно приложение 20..80% выделенной памяти не использует

то нужно оторвать руки этому рукожопому быдлокодеру! Нефиг лишнюю память выделять. Максимум — лишний килобайтик. А не гигабайты, как какой-нибудь говнохромой выделяет. Хромоногая скотина — вообще эталон быдлорукожопокода.

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

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

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

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

правда, есть опасность покоцать данные, если не хватит памяти

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

ē-моē! calloc имеет смысл делать, если ты выделяешь какую-нибудь структуру и хочешь чтобы там поля приняли дефолтные нулевые значения (в т.ч. всякие указатели были NULL, чтобы при обращении к ним не вышла жопа). А тупо под строку calloc'ом память выделять... Ну, если тебе влом дописать последний нуль в динамическую строку, еще ладно. Но у тебя же по сути статическая строка! Заполнил, дописал один-единственный нулик и готово!

если не делать memset, то может выйти нечто вроде «G .Hello World!» вместо «Hello World!»

Не может, т.к. ты с первого символа начинаешь заполнять. И пофиг, какая у тебя архитектура: LE, SE или вообще ME какая-нибудь. Байты — они и в Африке байты!

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

а нахрена memcpy, когда можно strcat?

strcat == strlen + memcpy. И нафига лишний раз вызывать strlen?

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

лень по коду искать

Все там читать лень, да и непонятные какие-то внутренние функции и макросы используются

Да ты упорот.

Хрень какая-то!

Учи матчасть.

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