sprintf() без нулевого пробела в конце в C

Есть ли способ использовать функцию C sprintf() без добавления символа «\ 0» в конце ее вывода? Мне нужно написать форматированный текст в середине строки фиксированной ширины.


person zaratustra    schedule 10.12.2008    source источник


Ответы (8)


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

person Greg Hewgill    schedule 10.12.2008
comment
Или используйте memmove() или, возможно, memcpy() вместо strncpy(). - person Jonathan Leffler; 10.12.2008
comment
Родди: Функция snprintf всегда завершает свой вывод нулевым значением. Если вы передадите размер n в snprintf, он напишет не более n-1 символов, за которыми следует завершающий '\0'. - person Greg Hewgill; 11.12.2008
comment
На самом деле этот пример не добавит null: char name[9] = QQ40dude; беззнаковое целое i0To100 = 63; _snprintf(&имя[2],2,%d,i0To100); printf(имя);// вывод: QQ63dude - person yan bellavance; 21.02.2015
comment
@yanbellavance: Ваш пример не работает, как вы утверждаете, и печатает просто QQ6. - person Greg Hewgill; 23.02.2015

sprintf возвращает длину написанной строки (не включая нулевой терминал), вы можете использовать это, чтобы узнать, где был нулевой терминал, и изменить символ нулевого терминала на что-то другое (например, пробел). Это было бы более эффективно, чем использование strncpy.

 unsigned int len = sprintf(str, ...);
 str[len] = '<your char here>';
person Doug T.    schedule 10.12.2008

Вы не можете сделать это с помощью sprintf(), но вы можете сделать это с помощью snprintf(), в зависимости от вашей платформы.

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

Это работает, потому что некоторые реализации snprintf() НЕ гарантируют запись завершающего символа - предположительно для совместимости с такими функциями, как stncpy().

char message[32] = "Hello 123, it's good to see you.";

snprintf(&message[6],3,"Joe");

После этого «123» заменяется на «Джо».

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

Большинство версий snprintf() для Windows демонстрируют такое поведение.

Но MacOS и BSD (и, возможно, Linux ), кажется, всегда заканчивается нулем.

person Roddy    schedule 11.12.2008
comment
Функция snprintf всегда завершает вывод null. После запуска приведенного выше кода сообщение содержит Hello Jo. - person Greg Hewgill; 11.12.2008
comment
@ Грег. Вы почти правы... Я обновил ответ, чтобы отразить зависимость от реализации. - person Roddy; 12.12.2008
comment
Вау, я никогда не знал, что это отличается на разных платформах, спасибо за это. - person Greg Hewgill; 12.12.2008
comment
Кто-нибудь знает, что диктуют спецификации POSIX и/или C99? - person Tommy; 26.10.2010
comment
Я считаю, что спецификации предписывают всегда добавлять нулевой терминатор (идея состоит в том, что строки всегда заканчиваются нулем и находятся в пределах границ, что предотвращает переполнение буфера). Однако большинство реализаций отличаются от стандарта либо отсутствием добавления \0, либо добавлением его после конца буфера. См. википедию: printf. - person sam; 09.02.2011
comment
Хотя в этом ответе это называется проблемой реализации, это, безусловно, не поведение, определяемое реализацией, как это определено поведением, определяемым реализацией в C, §3.4.2. Это случай несоблюдения. Для добавления указывается нулевой символ. Не делать этого — несоответствующее поведение. Кроме того, это не проблема ОС, а проблема компилятора. Код может работать как следует с компилятором Windows, хотя, возможно, и не с компилятором MS. - person chux - Reinstate Monica; 31.12.2017
comment
@чукс. В основном с этим согласен. Моему ответу 9 лет, и кажется, что MS наконец-то исправила это для соответствия. (строго говоря, проблема заключается в библиотеке времени выполнения C, а не в компиляторе, но они, как правило, идут рука об руку) docs.microsoft.com/en-us/cpp/c-runtime-library/reference/ - person Roddy; 09.01.2018

Вы также можете использовать строку фиксированной ширины в качестве строки формата, например:

char my_fixed_width_string_format[] = "need 10 chars starting here: %10s";
char my_fixed_width_string[40];
char string_to_print[] = "abcdefghijklmnop";
sprintf(my_fixed_width_string, my_fixed_width_string_format, string_to_print;
printf(my_fixed_width_string);

должен дать

нужно 10 символов, начиная здесь: abcdefghij

person Nathan Fellman    schedule 11.12.2008
comment
Это, вероятно, лучший способ сделать это. - person pjc50; 04.10.2010
comment
Предотвращает ли это нуль в конце sprintf()? или значение my_fixed_width_string + 41 затерто нулем? - person Bryan P; 31.08.2013

Поскольку вы пишете в фиксированную область, вы можете сделать это следующим образом:

// pointer to fixed area we want to write to
char* s;

// number of bytes needed, not including the null
int r = snprintf(0, 0, <your va_args here>);

// char following the last char we will write - null goes here
char c = s[r + 1];

// do the formatted write
snprintf(s, r + 1, <your_va_args here>);

// replace what was overwritten
s[r + 1] = c;
person Todd Freed    schedule 03.07.2012

На самом деле этот пример не добавит null, если вы используете snprintf:

char name[9] = "QQ40dude";  
unsigned int i0To100 = 63;  
_snprintf(&name[2],2,"%d",i0To100);  
printf(name);// output will be: QQ63dude  
person yan bellavance    schedule 21.02.2015
comment
К сожалению, результат QQ6 с POSIX snprintf(3). - person nodakai; 26.05.2015

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

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

Вы можете запустить следующий код здесь: https://rextester.com/AMOC49082

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

int main(void)
{
  char buf[100] = { 'a', 'b', 'c', 'd', 'e' };
  const size_t buf_size = sizeof(buf);
  const int i = 123;

  int result = snprintf(buf, 0, "%i", i);
  if (result < 0)
  {
    printf("snprintf error: %i\n", result);
    return -1;
  }

  int clobbered_index = result; //this index will get the null term written into it

  if (result >= buf_size)
  {
    printf("buffer not large enough. required %i chars\n", result + 1);
    return -1;
  }

  char temp_char = buf[clobbered_index];
  result = snprintf(buf, buf_size, "%i", i); //add result error checking here to catch future mistakes
  buf[clobbered_index] = temp_char;

  printf("buf:%s\n", buf);

  return 0;
}

Печатает buf:123de

person afk    schedule 27.12.2019

посмотрите здесь: http://en.wikipedia.org/wiki/Printf

printf("%.*s", 3, "abcdef") приведет к печати "abc"

person nir    schedule 04.10.2010
comment
строковые версии этих функций отличаются в этом отношении от нестроковых версий. Другими словами, sprintf и snprintf всегда добавляют нулевой байт, а printf — нет, поэтому ваш пример неприменим. - person Todd Freed; 04.07.2012