Ошибка сегментации при использовании setenv (C)

Это часть моего кода (где max — число с плавающей запятой):

        printf("noise level found: %f\n", max);

        //Put into "String"
        char var[21];
        sprintf(var, "%f", max);

        setenv("music_sync_soundcard_noise_level",var,1);

        printf("noise level written\n");

Что производит вывод:

noise level found: 2410965368832.000000
Segmentation fault

В то время как некоторые строки ранее у меня почти такие же:

printf("test finished, offset is %f\n", *offset);

//Put into "String"
char var[20];
sprintf(var, "%f", *offset);

setenv("music_sync_soundcard_offset",var,1);

Который работает без проблем.

// РЕДАКТИРОВАТЬ: Изменен размер массива, к сожалению, проблема не устранена


person Potheker    schedule 13.02.2020    source источник
comment
Массив символов недостаточно велик.   -  person Vlad from Moscow    schedule 13.02.2020
comment
Кроме того, я был бы осторожен с использованием setenv со значением стека.   -  person sturcotte06    schedule 13.02.2020
comment
по моим подсчетам, 2410965368832.000000 - это 20 символов, поэтому у вас 1 короткий (нужно место для терминатора NUL). Если вы не используете встроенную систему с чрезвычайно ограниченными ресурсами, память дешева и ее много, не беспокойтесь о попытке сэкономить несколько байтов, char var[64] или [128] должно быть более чем достаточно, и использование вашей памяти даже не будет мигать.   -  person yano    schedule 13.02.2020
comment
почему и как мне установить переменную среды в значение, вычисленное моим кодом?   -  person Potheker    schedule 13.02.2020
comment
Попробуйте "%f" --› "%e". (4 места)   -  person chux - Reinstate Monica    schedule 13.02.2020
comment
@ sturcotte06 setenv должно быть в порядке со значениями стека, поскольку он делает копии строк. (С putenv нужно быть осторожным, потому что putenv хранит фактическое значение char *.)   -  person Ian Abbott    schedule 13.02.2020
comment
@yano char var[64] или [128] --› или, возможно, char var[1 + 1 + DBL_MAX_10_EXP + 1 + 6 + 1] и справиться со всеми double с помощью "%f".   -  person chux - Reinstate Monica    schedule 13.02.2020
comment
@chux не сработал, все еще segfault   -  person Potheker    schedule 13.02.2020
comment
@chux-ReinstateMonica Конечно, я не знаю, какие там все ваши магические числа, но если это соответствует максимальной длине строки, которой может быть double, это лучшее решение.   -  person yano    schedule 13.02.2020
comment
Если ничего другого, используйте snprintf, чтобы, если буфер слишком мал, он усекался, а не разбивал стек...   -  person R.. GitHub STOP HELPING ICE    schedule 13.02.2020
comment
Скорее всего, у OP произошел сбой в коде, который следует за тем, что они нам не показывают...   -  person R.. GitHub STOP HELPING ICE    schedule 13.02.2020
comment
Мы не можем видеть, что вы меняете, и мы не знаем, что еще в вашем коде может иметь эффект. Создайте минимально воспроизводимый пример.   -  person Nate Eldredge    schedule 13.02.2020


Ответы (2)


Недостаточный размер буфера — это неопределенное поведение (UB). Этот УБ может проявляться в местах, далеких от исходного УБ.

Улучшите код, написав код, который не будет переполнять буфер — везде и для всех значений.

Предоставить достаточный буфер для всех max

#include <float.h>

float max;    
// char var[21];
char var[1 + 1+FLT_MAX_10_EXP                             + 1 + 6      + 1];
//       -   d dddddddddd dddddddddd dddddddddd dddddddd    .   fraction \0
sprintf(var, "%f", max);

Используйте snprint(), чтобы избежать переполнения буфера @Р..

// sprintf(var, "%f", max);
snprintf(var, sizeof var, "%f", max);

Надежный код проверит возвращаемое значение:

int len = snprintf(var, sizeof var, "%f", max);
if (len < 0 || (unsigned) len >= sizeof var) Oops();

Используйте "%e", чтобы справиться с большими и маленькими значениями с плавающей запятой и представить их правильно

Используйте экспоненциальное представление, чтобы избежать больших буферов. Используйте достаточную точность.

char var[1 + 1 + 1 + FLT_DECIMAL_DIG-1 + 1 + 1 + 4 + 1];
//       -   d   .   dddddddd            e   -  expo \0
sprintf(var, sizeof var, "%.*e", FLT_DECIMAL_DIG-1, max);
person chux - Reinstate Monica    schedule 13.02.2020
comment
поэтому я обнаружил, что первый setenv (который казался успешным) на самом деле является проблемой, когда я его комментирую, все работает нормально. Но я не могу заставить его работать, не комментируя его. Я попробовал ваши советы (хотя я не совсем понимаю чем, я не опытен) и даже когда я просто пишу sprintf(var, test); или вообще без sprintf, выдает какую-то ошибку (либо segfault, либо realloc(): недопустимый старый размер, либо что-то в этом роде, и они меняются каждый раз, когда я запускаю) - person Potheker; 13.02.2020
comment
о, ничего себе, теперь я понимаю, что размер UB может проявляться в местах, далеких от исходного UB, у меня было чтение звуковой карты со слишком коротким размером буфера за несколько строк до этого и с буфером, который не имел ничего общего с переменными, которые вызывал проблему. Довольно увлекательно. Спасибо! - person Potheker; 13.02.2020

Никогда не используйте такие функции, как sprintf() и strcpy(), которые не принимают аргумент, указывающий размер буфера. Всегда используйте snprintf(), strlcpy() и т. д. и проверяйте возвращаемое значение на наличие ошибок. Таким образом, если буфер слишком мал, вы можете справиться с этим, а не просто предполагать, что каракули в случайных ячейках памяти после конца вашего буфера не вызовут никаких проблем. Как отметил @chux, то, что вы делаете, приводит к неопределенному поведению. Также известна как ситуация остановиться и загореться (HCF).

Также обратите внимание, что это не связано с чем-то, что делает setenv(). Ошибка в том, что sprintf() пишет за конец буфера, который вы ему дали. Буквально все, что вы делаете после этого вызова sprintf(), может потерпеть неудачу. Включая просто возврат из функции, которая сделала этот sprintf() вызов.

person Kurtis Rader    schedule 14.02.2020
comment
strcpy нормально, если вы проверили длину исходного кода - person M.M; 14.02.2020
comment
@ ММ Неправильно. strcpy() никогда не следует использовать. Отчасти потому, что люди, которые его используют, по-прежнему могут иметь ошибки «один за другим», потому что они не учитывают завершающий нулевой байт при расчете длины источника. Сегодня ни один компетентный инженер-программист не стал бы использовать его. - person Kurtis Rader; 15.02.2020
comment
Компетентные инженеры-программисты знают о нулевых терминаторах, поэтому ваше утверждение кажется противоречивым. А с snprintf, strlcpy и т. д. возможна ошибка «одна за другой». - person M.M; 15.02.2020
comment
@M.M. Я очень надеюсь, что вы не зарабатываете на жизнь программным обеспечением. Ошибка на одну ошибку в обычном понимании этого термина невозможна с snprintf(), strlcpy() и т. д. Да, вы можете вызвать проблемы при использовании этих функций, солгав о длине буфера, но обычно это не рассматривается выключение на одну ошибку. - person Kurtis Rader; 15.02.2020
comment
Отличительной особенностью strcpy является то, что вы не можете солгать о длине буфера. - person M.M; 15.02.2020
comment
вот сегодняшний пример. Какие изменения вы собираетесь порекомендовать для этого кода без каких-либо возможных ошибок в настоящее время? Использовать другую функцию, которая создает еще одну точку отказа? - person M.M; 15.02.2020
comment
@M.M Ха-ха-ха! Вы меня поняли :-) Шучу. Очевидно, вы думаете, что, когда я сказал, что strcpy() никогда не следует использовать, я имел в виду, что это применимо даже в таких ситуациях, как надуманный пример, который вы нашли. Довольно глупая интерпретация. Тем более, что в этом примере нужно просто использовать strdup(). Использование malloc(), за которым следует strcpy(), довольно глупо. - person Kurtis Rader; 16.02.2020
comment
strdup нет в ISO C. И есть много других подобных ситуаций, когда известно, что длины безопасны из-за предыдущего кода. - person M.M; 16.02.2020
comment
@MM strdup() недавно был добавлен в стандарт ISO C, и я никогда не сталкивался с системой без него. Я программирую в системах на базе UNIX с 1985 года. Несмотря на это, использование strcpy() глупо, поскольку пример, на который вы ссылаетесь, уже определил длину строки. Было бы более эффективно использовать memcpy(). Вы также намеренно тупите, поскольку такая фраза, как никогда не использовать strcpy(), не предназначена для того, чтобы означать, что не существует мыслимой ситуации, в которой это неуместно. Только то, что его нельзя использовать без чертовски веской причины. Что будет очень редко. - person Kurtis Rader; 17.02.2020
comment
strdup нет ни в одном опубликованном стандарте ISO C (его планируется включить в следующий стандарт). Вы написали никогда, никогда в своем ответе, это довольно сильное утверждение. И чертовски веская причина, очевидно, субъективна, как и определение компетентного инженера-программиста. Компилятор должен оптимизировать подсчет длины, если это возможно, смысл использования strcpy в этой ситуации заключается в том, что его легко читать и нет возможности ошибки, по сравнению с использованием memcpy плюс набор нулевых терминаторов, где вы можете сделать различную длину и смещение ошибки. - person M.M; 17.02.2020
comment
stackoverflow.com/questions/60281429/ - person M.M; 18.02.2020